<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Pengzhan</title>
        <link>https://pengzhan.dev</link>
        <description>Was childish, Now foolish.</description>
        <lastBuildDate>Tue, 22 Sep 2026 09:43:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Pengzhan</title>
            <url>https://pengzhan.dev/images/og-default.png</url>
            <link>https://pengzhan.dev</link>
        </image>
        <copyright>All rights reserved 2026, Pengzhan Hao</copyright>
        <item>
            <title><![CDATA[AI can change: Coding for kids]]></title>
            <link>https://pengzhan.dev/ai-can-change-coding-for-kids</link>
            <guid>https://pengzhan.dev/ai-can-change-coding-for-kids</guid>
            <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Since the explosion of Large Language Models (LLMs), a wave of disruption has quietly swept through every industry. What does it mean for everyday life and parenting?]]></description>
            <content:encoded><![CDATA[<p>Since the explosion of Large Language Models (LLMs), a wave of disruption has quietly swept through every industry. While it is impossible to list every sector currently in flux, the sheer volume of new demands from startups and the AI-driven overhauls of traditional businesses serve as constant reminders of the magnitude of this shift. However, unlike the macro-narrative of business-driven AI, the actual impact of LLMs on the average person remains somewhat elusive—even for programmers standing at the forefront of this change.</p>
<p>Two weeks ago, while taking care of my child alone, I noticed a letter-tracing worksheet she brought home from daycare. It hit me that she was starting to grasp the concept of “homework.” In the United States, expecting a daycare to provide Chinese writing practice is almost a luxury. To help her build a foundational feel for writing Chinese characters, I decided to follow the pace of children growing up in China and create some practice sheets for her. After searching online, I found that most Chinese calligraphy worksheets are designed for older students or adults. For a child who can barely hold a pen steady, writing small characters is simply too challenging. I found a site called <a href="https://www.nqez.com/">E-Zitie</a> that offered exactly what I needed: <a href="https://www.nqez.com/zitie-hanzibishunlizi.html">large characters with stroke order guides</a>. However, while the basic service is free, any customization requires a paid membership. Although the 38 RMB/year fee is negligible, the website’s UI felt like something from three generations ago, making me doubt its ability to meet my long-term needs.</p>
<p>Last week, a thought struck me: Why not build this tool myself? For a programmer with a background in infrastructure and backend development, building a web app is actually quite a daunting task. In my previous personal projects, the most time-consuming part was always the meticulous frontend tweaking. Even setting aside the frontend, architecting a backend that meets professional engineering standards is an exhausting endeavor. Whenever I thought about the sheer volume of work involved, my inner laziness would whisper: <em>Just pay for the membership.</em></p>
<p>But this time, I started with an unusual level of confidence. My recent experiences using language models and practicing “<a href="https://x.com/karpathy/status/1886192184321433842">Vibe Coding</a>” (as coined by Andrej Karpathy) taught me that AI is already incredibly capable of building prototypes. So, during the gaps in my parenting duties, I wrote a 300-word requirement and design doc. What followed was a highly efficient dialogue: about 10 minutes of AI generation and another 10 minutes of fine-tuning. <a href="https://github.com/google-gemini/gemini-cli">Gemini-CLI</a>, powered by the latest Gemini Pro model, helped me stand up a clean, functional website in no time.</p>
<p><img src="/posts-assets/ai-can-change-coding-for-kids/images/toolbox-index-1024x590.png" alt="Home interface of the calligraphy practice generator" title="*Home interface of the calligraphy practice generator*"></p>
<p><img src="/posts-assets/ai-can-change-coding-for-kids/images/zitie-example.png" alt="AI-generated Chinese character tracing PDF with stroke order" title="*AI-generated Chinese character tracing PDF with stroke order*"></p>
<p><strong>The project is currently hosted on my personal domain and can be accessed at <a href="https://toolbox.pengzhan.dev">toolbox.pengzhan.dev</a>.</strong></p>
<p>In less than 30 minutes, I was already printing out tracing sheets for my child to doodle on. This sense of instant gratification gave me a deeper realization of how AI will shape our future:</p>
<ul>
<li>
<p><strong>Software creation is becoming a task anyone can perform, regardless of coding ability.</strong> This means that as long as you are willing to spend a few cents on LLM API calls, you can easily build what was traditionally considered a “program.” The barrier to entry for programming has dropped significantly. More importantly, when everyone can be a Product Manager, the question of “who implements it” becomes secondary. Take education as an example: if you are a physics teacher explaining force analysis, an editable PPT might capture your logic, but it’s hard to make it look right. Existing educational software might demonstrate the concept, but it might not align perfectly with your teaching style. So, why not let AI build a bespoke, web-based teaching demo just for you?</p>
</li>
<li>
<p><strong>Highly customized and truly innovative needs will define the human contribution to future software engineering.</strong> Since basic needs are now easily met, where does human value lie? Clearly, AI still struggles with unique, highly specific requirements or completely innovative explorations. Even when AI can “stitch together” a solution, the process is often laborious and requires constant human guidance. For instance, because AI still lacks precision in visual positioning and coordinate handling, the generated PDF had a persistent offset bug that AI couldn’t fix. Despite my lack of familiarity with the <a href="https://github.com/signintech/gopdf">gopdf</a> library, I had to dive into the source code myself to identify the error in 2D coordinate calculations. This “last mile” limitation is the current bottleneck of AI. Similarly, for completely unknown technical problems—like a Unix Socket error I encountered last week—LLMs (at least the latest <a href="https://blog.google/technology/ai/google-gemini-3-1-pro-update/">Gemini-3.1-Pro</a>) still cannot find a breakthrough using purely statistical logic. That human “spark”—an efficient activation of massive, compressed knowledge—remains far beyond the reach of current AI architectures.</p>
</li>
<li>
<p><strong>As LLMs enter the next phase and supporting tools mature, the very form of education will undergo a massive transformation.</strong> The power of LLMs is both transformative and “disruptive.” My initial goal was just to satisfy a need for writing practice, but the success of this tool made me realize the possibilities are much broader. Recently, I noticed my child struggling with counting worksheets from school; she often didn’t understand the instructions, leading her to color the wrong items or fail to fill in the counts correctly. So, I customized a new feature for her: I let the AI randomly generate similar counting exercises. This approach—completely on-demand and randomly generated—not only reinforces the skill but also prevents “false mastery” caused by repeating the same static problems. This is the true cornerstone of “student-centered” personalized education: it’s no longer just a theoretical ideal, but a tangible infrastructure we can finally build.</p>
</li>
</ul>
<p><img src="/posts-assets/ai-can-change-coding-for-kids/images/in-school-counting-scaled-e1772402564249-859x1024.png" alt="Counting practice sheet from school; instructions are somewhat ambiguous" title="*Counting practice sheet from school; instructions are somewhat ambiguous*"></p>
<p><img src="/posts-assets/ai-can-change-coding-for-kids/images/practice-ai-generated-counting-scaled-e1772402666560-782x1024.png" alt="Customized counting exercise randomly generated by AI for the same skill set" title="*Customized counting exercise randomly generated by AI for the same skill set*"></p>
<p><strong>We cannot predict exactly what comes next. But I stand in awe of this wave of change, and I am deeply excited to witness the next evolution of human society.</strong></p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/ai-can-change-coding-for-kids/images/cover-en.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Using Gemini-CLI and MCP server to organize my dropbox]]></title>
            <link>https://pengzhan.dev/using-gemini-cli-and-mcp-server-to-organize-my-dropbox</link>
            <guid>https://pengzhan.dev/using-gemini-cli-and-mcp-server-to-organize-my-dropbox</guid>
            <pubDate>Sun, 31 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[I think many people face the same dilemma I did: we don’t want to lose data, so we simply dump everything into cloud storage (like Dropbox(https://www.dropbox.com/)). The same thin...]]></description>
            <content:encoded><![CDATA[<p>I think many people face the same dilemma I did: we don’t want to lose data, so we simply dump everything into cloud storage (like <a href="https://www.dropbox.com/">Dropbox</a>). The same thing happens when I back up my computer — I often do it in bulk, with big gaps between backups. Each time, I come up with a slightly different way of organizing files. Over the years, this inconsistency has turned my cloud storage into a dumping ground, wasting both space and mental bandwidth.</p>
<p>Another pain point is photos. Every time I consider organizing them — deleting the meaningless ones and sorting the rest — I get overwhelmed by the sheer size of the task and put it off.</p>
<p>This is exactly the kind of tedious work AI is great at. Unlike more creative tasks where large language models (LLMs) can hallucinate, organizing files is straightforward and rules-based. It felt natural to me that AI should be able to help. For a long time, though, I didn’t know how to get started.</p>
<p>When agent modes became mainstream in AI platforms, I thought I had found the answer. But after trying ChatGPT’s web-based agent, I was shocked at how clumsy it was — watching it fail to click a simple checkbox in the Dropbox web client was painful. After wasting two hours, I gave up.</p>
<p>That’s when I started looking into the <a href="https://modelcontextprotocol.io/">MCP protocol</a> and realized it could help. Unlike GUI-based agents that burn tokens mimicking mouse clicks, MCP makes everything machine-readable and structured. After a bit of research, I decided to try <a href="https://github.com/google-gemini/gemini-cli">Gemini-CLI</a> (which recently topped GitHub’s trending charts) together with a third-party <a href="https://github.com/amgadabdelhafez/dbx-mcp-server">Dropbox MCP server</a>.</p>
<h2 id="quick-setup" tabindex="-1">Quick Setup <a class="header-anchor" href="#quick-setup" aria-hidden="true">#</a></h2>
<h3 id="installing-gemini-cli" tabindex="-1">Installing Gemini-CLI <a class="header-anchor" href="#installing-gemini-cli" aria-hidden="true">#</a></h3>
<p>This part is straightforward. Just follow Gemini-CLI’s <a href="https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#-installation">GitHub README</a>.
I tested on my MacBook and simply used Homebrew to install it:</p>
<pre class="language-bash"><code class="language-bash">brew <span class="token function">install</span> gemini-cli
</code></pre>
<p>After installation, you need to authenticate Gemini-CLI. You can either:</p>
<ul>
<li>
<p>Run <code>gemini-cli</code>, type <code>/auth</code>, then select <strong>1. Login with Google</strong></p>
</li>
<li>
<p>Or use your <a href="https://ai.google.dev/gemini-api/docs/api-key">Gemini API Key</a> (see the official tutorial)</p>
</li>
</ul>
<h3 id="running-mcp-server" tabindex="-1">Running MCP Server <a class="header-anchor" href="#running-mcp-server" aria-hidden="true">#</a></h3>
<p>Instructions for the Dropbox MCP server can be found in its README, or you can follow these steps:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/amgadabdelhafez/dbx-mcp-server.git
<span class="token builtin class-name">cd</span> dbx-mcp-server
<span class="token function">npm</span> <span class="token function">install</span>
<span class="token function">npm</span> run build
<span class="token function">npm</span> run setup
</code></pre>
<p>During setup, you’ll need to create a Dropbox app in the <a href="https://www.dropbox.com/developers/apps">Dropbox App Console</a>. Make sure to grant all required permissions, then provide the details back to the interactive shell when prompted.</p>
<p>Once that’s done, the setup will open a browser window asking you to authenticate your Dropbox app. After login, you’ll be redirected to a local URL like this:</p>
<pre class="language-text"><code class="language-text">http://localhost/?code=WBjFx90NRQwdv8TM71kf343coS7Q5J2l8Wnqw
</code></pre>
<p>Copy the <code>code</code> back into your shell to complete the configuration.
I recommend selecting <strong>1. Generate Claude Desktop config</strong> when asked, which will automatically update your Claude configuration:</p>
<pre class="language-text"><code class="language-text">Would you like to generate MCP configuration files?
1. Generate Claude Desktop config
2. Generate Cline config
3. Generate both
4. Skip

Enter your choice (1-4): 1
</code></pre>
<p>You’ll then see confirmation that the Claude Desktop config has been updated.</p>
<h3 id="configuring-gemini-for-mcp-servers" tabindex="-1">Configuring Gemini for MCP Servers <a class="header-anchor" href="#configuring-gemini-for-mcp-servers" aria-hidden="true">#</a></h3>
<p>Now copy the MCP configuration into Gemini-CLI’s <code>settings.json</code> file located at <code>~/.gemini/settings.json</code>.
Make sure to allow the <code>dbx-mcp-server</code> in the <code>mcp</code> section, for example:</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
  ...
  <span class="token property">"mcp"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token property">"allowed"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token string">"dbx-mcp-server"</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token property">"excluded"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token property">"mcpServers"</span><span class="token operator">:</span> <span class="token punctuation">{</span>   <span class="token comment">// Copied from Claude Desktop config</span>
    <span class="token property">"dbx-mcp-server"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token property">"command"</span><span class="token operator">:</span> <span class="token string">"node"</span><span class="token punctuation">,</span>
      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token string">"/tmp/Github/dbx-mcp-server/build/src/index.js"</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token property">"env"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token property">"DROPBOX_APP_KEY"</span><span class="token operator">:</span> <span class="token string">"..."</span><span class="token punctuation">,</span>
        <span class="token property">"DROPBOX_APP_SECRET"</span><span class="token operator">:</span> <span class="token string">"..."</span><span class="token punctuation">,</span>
        <span class="token property">"DROPBOX_REFRESH_TOKEN"</span><span class="token operator">:</span> <span class="token string">"..."</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token property">"disabled"</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
      <span class="token property">"autoApprove"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Finally, restart Gemini-CLI. Run the command <code>/mcp list</code> and you should see the <code>dbx-mcp-server</code> listed as ready, with its available tools:</p>
<pre class="language-text"><code class="language-text">Configured MCP servers:

  🟢 dbx-mcp-server - Ready (13 tools, 2 prompts)
    Tools:
    - copy_item
    - create_folder
    - delete_item
    - download_file
    - get_account_info
    - get_file_content
    - get_file_metadata
    - get_sharing_link
    - list_files
    - move_item
    - safe_delete_item
    - search_file_db
    - upload_file
</code></pre>
<h2 id="how-i-use-it" tabindex="-1">How I Use It <a class="header-anchor" href="#how-i-use-it" aria-hidden="true">#</a></h2>
<p>I’m not going to dive deep into how MCP works — I’m a newbie myself. But I did find this <a href="https://www.descope.com/learn/post/mcp">blog post</a> extremely easy to follow, especially with its self-explanatory flow chart:</p>
<p><img src="https://images.ctfassets.net/xqb1f63q68s1/5uCDzIdxqiMSlIXx2Z9w73/21bd511971ecc7115f2edee0bdc8a17c/MCP_Claude_Mermaid_Chart.png" alt="MCP Flow Chart"></p>
<p>Below are some of the prompts I’ve tried and what I learned from them.</p>
<hr>
<h3 id="prompt-1" tabindex="-1">Prompt 1 <a class="header-anchor" href="#prompt-1" aria-hidden="true">#</a></h3>
<p><strong>Prompt:</strong> Could you analyze all my major folders (top 3 levels) and give me a new directory tree structure I can further use?</p>
<p><strong>Result:</strong></p>
<ul>
<li>❌ The folder suggestions still looked strange and needed a lot of guidance.</li>
</ul>
<h3 id="prompt-2" tabindex="-1">Prompt 2 <a class="header-anchor" href="#prompt-2" aria-hidden="true">#</a></h3>
<p><strong>Prompt:</strong> There are a lot of unorganized files in this folder. Could you categorize them in a general way that avoids overcomplicating or fragmenting into too many folders or nested folders? After that, generate a plan on how to move them and ask for my approval before execution.</p>
<p><strong>Result:</strong></p>
<ul>
<li>
<p>✅ The assistant generated a plan and asked for feedback.</p>
</li>
<li>
<p>I provided additional suggestions:</p>
</li>
</ul>
<p>Took ~20 conversation rounds before it could reliably categorize based on file content.</p>
<ul>
<li>
<p>⚠️ <strong>Privacy note:</strong> This prompt may leak personal information to the LLM service. Be cautious.</p>
</li>
<li>
<p>Time: ~20 minutes of planning, ~400 files categorized. (Execution time not measured — I multitasked while it ran.)</p>
</li>
</ul>
<h3 id="prompt-3" tabindex="-1">Prompt 3 <a class="header-anchor" href="#prompt-3" aria-hidden="true">#</a></h3>
<p><strong>Prompt:</strong> Could you scan all non-target folders, find empty ones, list them, and then delete them after my review?</p>
<p><strong>Result</strong>: ✅ Successfully executed and correctly removed empty folders.</p>
<h3 id="prompt-4" tabindex="-1">Prompt 4 <a class="header-anchor" href="#prompt-4" aria-hidden="true">#</a></h3>
<p><strong>Prompt:</strong> Find all folders that could be merged based on name similarity (and path).</p>
<p><strong>Result:</strong> ✅ Found 16 folder pairs that could be merged.</p>
<h2 id="personal-feelings-and-conclusion" tabindex="-1">Personal Feelings and Conclusion <a class="header-anchor" href="#personal-feelings-and-conclusion" aria-hidden="true">#</a></h2>
<p>Even though MCP + Gemini-CLI can achieve very good performance on some tasks, it’s still difficult for even the most advanced LLMs to fully understand situations and consistently make the right calls based on their own analysis. Human input is still essential.</p>
<p>Beyond that, I had five strong personal takeaways:</p>
<ul>
<li></li>
</ul>
<p><strong>Prompt size limitations</strong>
Even with a 2M token context, it’s tiny compared to human memory. Using flat, non-hierarchical memory structures for LLMs is not scalable — both logically and infrastructurally.</p>
<ul>
<li></li>
</ul>
<p><strong>MCP’s verbosity</strong>
While MCP is a solid connector schema, it’s verbose and sometimes makes the context size problem worse.</p>
<ul>
<li></li>
</ul>
<p><strong>Good for general tasks, risky for details</strong>
This approach works great for general asks, but struggles with detailed organization tasks like renaming or fine-grained document sorting. For example, it once miscategorized some of my identity documents (an uncommon format) into a tax folder — a serious mistake.</p>
<ul>
<li></li>
</ul>
<p><strong>Weak multimedia handling</strong>
Feeding in entire videos, large photo sets, or even a bulk of small photos from one trip doesn’t work well. This reminded me of how most YouTube summarizers today are still transcript-based rather than visual-based.</p>
<ul>
<li></li>
</ul>
<p><strong>Privacy concerns</strong>
Privacy is a major pain point. Even when using my own API key or self-deployed services, the risk of leaking sensitive personal information during file organization (or similar workflows) still feels dangerous.</p>
<hr>
<p>Overall, MCP + Gemini-CLI is powerful and promising for Dropbox organization, but it’s not a complete replacement for careful human oversight. For now, I see it as an assistant — not an autopilot.</p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/using-gemini-cli-and-mcp-server-to-organize-my-dropbox/images/cover.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Troubleshooting GKE Pods in CrashLoopBackOff: How to Start with Strace]]></title>
            <link>https://pengzhan.dev/troubleshooting-gke-pods-in-crashloopbackoff-how-to-start-with-strace</link>
            <guid>https://pengzhan.dev/troubleshooting-gke-pods-in-crashloopbackoff-how-to-start-with-strace</guid>
            <pubDate>Sun, 30 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[In most real-world Kubernetes cases, infrastructure engineers don't need to understand how a container itself works. This is reasonable because container image developers are typic...]]></description>
            <content:encoded><![CDATA[<p>In most real-world <em>Kubernetes</em> cases, infrastructure engineers don’t need to understand how a container itself works. This is reasonable because container image developers are typically responsible for it. However, a problem often arises when a container is deployed on <em>Kubernetes</em>, which is such a complex deployment and orchestration system. Sometimes, unexpected components can influence a container’s behavior.</p>
<p>Debugging an unexpected container is feasible for self-hosted <em>Kubernetes</em>, where engineers have better observability and testing capability. However, if you are using <em>GKE</em> or another cloud provider’s <em>Kubernetes</em> engine, the situation can become complicated.</p>
<p>Here’s a problem I recently faced.</p>
<h2 id="the-challenge%3A-debugging-a-crashing-vllm-container-on-gke" tabindex="-1">The Challenge: Debugging a Crashing vLLM Container on GKE <a class="header-anchor" href="#the-challenge%3A-debugging-a-crashing-vllm-container-on-gke" aria-hidden="true">#</a></h2>
<p>Recently, I deployed a <em>Large Language Model (LLM)</em> model serving container on <em>GKE</em> as a Deployment. When I ran the serving framework in a non-containerized environment, everything worked perfectly, but it consistently crashed when running inside a <em>Kubernetes</em> pod.</p>
<p>The most challenging aspect of the situation was that no application stdout or logs were being generated by the application. The only information I had was from <code>kubectl</code>, which showed that the container had exited with status code 1.‘</p>
<pre class="language-bash"><code class="language-bash">$ kubectl get pods
NAME READY STATUS RESTARTS AGE
meta-deployment-789d98c8f7-4fsz4 <span class="token number">0</span>/1 CrashLoopBackOff <span class="token number">5</span> 10m
</code></pre>
<p>To better understand why this issue was happening, I tried the same container with different entry points and commands. I found that the issue was highly related to the keyword “<em>vllm</em>”. Every time a command contained the string “<em>vllm</em>”, it would crash (except when I used <code>echo vllm</code>). It seemed that <code>echo</code> worked because it’s a shell built-in command, but any command that involved <code>exec</code> crashed.</p>
<p>With this context, I decided to use <code>gdb</code> it to debug the container. To do that, I needed to rebuild the container image on top of the existing one. However, even running <code>gdb --args python -m vllm.entrypoints.api_server ....</code> still resulted in a crash. To move forward, I decided to rely on <strong><a href="https://man7.org/linux/man-pages/man1/strace.1.html"><em>strace</em></a></strong>, a kernel tracing utility, to understand what was happening.</p>
<p>Here’s the tricky part: <em>GKE</em>’s default worker node OS image is <em><strong><a href="https://cloud.google.com/container-optimized-os/docs">COS (Container-Optimized OS)</a></strong></em> , a Debian-based Linux OS with many restrictions. For example, it lacks binary/package managers, and most paths under its file system are non-executable. So, how could I easily download and use <em>strace</em>?</p>
<h2 id="step-by-step-guide%3A-debugging-gke-crashloopbackoff-with-strace" tabindex="-1">Step-by-Step Guide: Debugging GKE CrashLoopBackOff with Strace <a class="header-anchor" href="#step-by-step-guide%3A-debugging-gke-crashloopbackoff-with-strace" aria-hidden="true">#</a></h2>
<p>Since we were already on a containerized platform, the simplest approach was to deploy another container and perform all operations within it. This container also needed to fulfill three criteria:</p>
<ul>
<li>
<p>The <em>strace</em> pod must be privileged.</p>
</li>
<li>
<p>The <em>strace</em> pod must have the <code>SYS_PTRACE</code> Linux capability.</p>
</li>
<li>
<p>The <em>strace</em> pod must be in the host PID namespace to view all processes.</p>
</li>
</ul>
<p>Here’s an example of pod I used</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">cat</span> <span class="token file-descriptor important">&amp;1</span> <span class="token operator">|</span> <span class="token function">tee</span> strace.log <span class="token comment"># pid to the target container, e.g. 4128</span>

<span class="token comment"># Go back to target pod's shell</span>
root@meta-deployment-789d98c8f7-4fsz4:/$ python <span class="token parameter variable">-m</span> vllm.entrypoints.api_server <span class="token punctuation">..</span><span class="token punctuation">..</span>

<span class="token comment"># Inside strace pod</span>
wait<span class="token punctuation">(</span>-1, 
fork<span class="token punctuation">(</span><span class="token punctuation">..</span><span class="token punctuation">..</span>
</code></pre>
<p>After that, I was able to examine the <em>strace</em> log to understand why and when my container was crashing.</p>
<h2 id="summary" tabindex="-1">Summary <a class="header-anchor" href="#summary" aria-hidden="true">#</a></h2>
<p>In this post, I explained a complicated scenario and the limitations of <em>GKE</em> when debugging it. I also described the solution to overcome these debugging difficulties by enabling the powerful <em>strace</em> utility on a <em>GKE</em> node. As for the reason behind this strange behavior, that will be discussed in a later post. It’s a different but interesting problem.</p>
<h2 id="cross-references" tabindex="-1">Cross-references <a class="header-anchor" href="#cross-references" aria-hidden="true">#</a></h2>
<p>[1] vLLM deployment example from <a href="https://cloud.google.com/model-garden?hl=en">GCP Vertex Model Garden</a></p>
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> meta<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">1</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> meta<span class="token punctuation">-</span>server
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> meta<span class="token punctuation">-</span>server
        <span class="token key atrule">ai.gke.io/model</span><span class="token punctuation">:</span> Llama<span class="token punctuation">-</span>3<span class="token punctuation">-</span>1<span class="token punctuation">-</span>8B<span class="token punctuation">-</span>Instruct
        <span class="token key atrule">ai.gke.io/inference-server</span><span class="token punctuation">:</span> vllm
        <span class="token key atrule">examples.ai.gke.io/source</span><span class="token punctuation">:</span> model<span class="token punctuation">-</span>garden
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> inference<span class="token punctuation">-</span>server
        <span class="token key atrule">image</span><span class="token punctuation">:</span> us<span class="token punctuation">-</span>docker.pkg.dev/vertex<span class="token punctuation">-</span>ai/vertex<span class="token punctuation">-</span>vision<span class="token punctuation">-</span>model<span class="token punctuation">-</span>garden<span class="token punctuation">-</span>dockers/pytorch<span class="token punctuation">-</span>vllm<span class="token punctuation">-</span>serve<span class="token punctuation">:</span>20240821_1034_RC00
        <span class="token key atrule">resources</span><span class="token punctuation">:</span>
          <span class="token key atrule">requests</span><span class="token punctuation">:</span>
            <span class="token key atrule">cpu</span><span class="token punctuation">:</span> <span class="token number">8</span>
            <span class="token key atrule">memory</span><span class="token punctuation">:</span> 29Gi
            <span class="token key atrule">ephemeral-storage</span><span class="token punctuation">:</span> 80Gi
            <span class="token key atrule">nvidia.com/gpu</span> <span class="token punctuation">:</span> <span class="token number">1</span>
          <span class="token key atrule">limits</span><span class="token punctuation">:</span>
            <span class="token key atrule">cpu</span><span class="token punctuation">:</span> <span class="token number">8</span>
            <span class="token key atrule">memory</span><span class="token punctuation">:</span> 29Gi
            <span class="token key atrule">ephemeral-storage</span><span class="token punctuation">:</span> 80Gi
            <span class="token key atrule">nvidia.com/gpu</span> <span class="token punctuation">:</span> <span class="token number">1</span>
        <span class="token key atrule">command</span><span class="token punctuation">:</span>
        <span class="token key atrule">args</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> python
        <span class="token punctuation">-</span> <span class="token punctuation">-</span>m
        <span class="token punctuation">-</span> vllm.entrypoints.api_server
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>host=0.0.0.0
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>port=7080
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>swap<span class="token punctuation">-</span>space=16
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>gpu<span class="token punctuation">-</span>memory<span class="token punctuation">-</span>utilization=0.9
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>max<span class="token punctuation">-</span>model<span class="token punctuation">-</span>len=32768
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>trust<span class="token punctuation">-</span>remote<span class="token punctuation">-</span>code
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>disable<span class="token punctuation">-</span>log<span class="token punctuation">-</span>stats
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>model=gs<span class="token punctuation">:</span>//vertex<span class="token punctuation">-</span>model<span class="token punctuation">-</span>garden<span class="token punctuation">-</span>public<span class="token punctuation">-</span>us/llama3.1/Meta<span class="token punctuation">-</span>Llama<span class="token punctuation">-</span>3.1<span class="token punctuation">-</span>8B<span class="token punctuation">-</span>Instruct
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>tensor<span class="token punctuation">-</span>parallel<span class="token punctuation">-</span>size=1
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>max<span class="token punctuation">-</span>num<span class="token punctuation">-</span>seqs=12
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>enforce<span class="token punctuation">-</span>eager
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>disable<span class="token punctuation">-</span>custom<span class="token punctuation">-</span>all<span class="token punctuation">-</span>reduce
        <span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>enable<span class="token punctuation">-</span>chunked<span class="token punctuation">-</span>prefill
        <span class="token key atrule">env</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MODEL_ID
          <span class="token key atrule">value</span><span class="token punctuation">:</span> <span class="token string">"meta-llama/Llama-3.1-8B-Instruct"</span>
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> DEPLOY_SOURCE
          <span class="token key atrule">value</span><span class="token punctuation">:</span> <span class="token string">"UI_NATIVE_MODEL"</span>
        <span class="token key atrule">volumeMounts</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">mountPath</span><span class="token punctuation">:</span> /dev/shm
          <span class="token key atrule">name</span><span class="token punctuation">:</span> dshm
      <span class="token key atrule">volumes</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> dshm
        <span class="token key atrule">emptyDir</span><span class="token punctuation">:</span>
          <span class="token key atrule">medium</span><span class="token punctuation">:</span> Memory
      <span class="token key atrule">nodeSelector</span><span class="token punctuation">:</span>
        <span class="token key atrule">cloud.google.com/gke-accelerator</span><span class="token punctuation">:</span> nvidia<span class="token punctuation">-</span>l4
<span class="token punctuation">---</span>
<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> meta<span class="token punctuation">-</span>service
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">app</span><span class="token punctuation">:</span> meta<span class="token punctuation">-</span>server
  <span class="token key atrule">type</span><span class="token punctuation">:</span> ClusterIP
  <span class="token key atrule">ports</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">protocol</span><span class="token punctuation">:</span> TCP
    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">8000</span>
    <span class="token key atrule">targetPort</span><span class="token punctuation">:</span> <span class="token number">7080</span>
</code></pre>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/troubleshooting-gke-pods-in-crashloopbackoff-how-to-start-with-strace/images/cover.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Kubernetes: Two Years In, Myths, and the Complexity Conundrum]]></title>
            <link>https://pengzhan.dev/kubernetes-two-years-in-myths-and-the-complexity-conundrum</link>
            <guid>https://pengzhan.dev/kubernetes-two-years-in-myths-and-the-complexity-conundrum</guid>
            <pubDate>Sat, 03 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[It's been two years since I stepped into the Kubernetes(https://kubernetes.io/) world, and during that time, I've been labeled the "Kubernetes Engineer" among friends. It's no surp...]]></description>
            <content:encoded><![CDATA[<p>It’s been two years since I stepped into the <a href="https://kubernetes.io/">Kubernetes</a> world, and during that time, I’ve been labeled the “Kubernetes Engineer” among friends. It’s no surprise that people either have zero knowledge about K8s or simply recognize the name, usually followed by “that overly complex system.”</p>
<p>As I hit this two-year milestone, I wanted to share my reflections on working in this field. This isn’t specific to <a href="https://cloud.google.com/kubernetes-engine">GKE</a> but more about Kubernetes as a whole. I’m still a relative newbie, but here are some stereotypes, myths, and pain points I’ve encountered:</p>
<h2 id="the-customization-conundrum%3A-complexity-is-king" tabindex="-1">The Customization Conundrum: Complexity is King <a class="header-anchor" href="#the-customization-conundrum%3A-complexity-is-king" aria-hidden="true">#</a></h2>
<p>Kubernetes was designed to orchestrate containerized workloads, scaling them and allocating resources seamlessly. But over its ten years, Kubernetes has evolved into a giant, complex beast. Years of user requests and feature additions have made it adaptable to various scenarios, but at the cost of simplicity.</p>
<p>One example that suddenly comes to mind is container types. Apparently, there are two types of containers in nature. <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/">InitContainer</a> is for the setup work of a pod before the main container runs. The main container is just the workloads that are containerized to be run to serve users’ needs. And after that, <a href="https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/">Ephemeral containers</a> and <a href="https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/">sidecar containers</a> (a special type of initContainer) were introduced for some special needs. These changes significantly increase K8s container lifecycle’s customization, but at what cost? I guess that the pod lifecycle becomes more complex, and new features will only serve some complex cases, which will confuse “orthodoxy” users.</p>
<h2 id="the-dream-gap%3A-kubernetes-for-the-big-leagues" tabindex="-1">The Dream Gap: Kubernetes for the Big Leagues <a class="header-anchor" href="#the-dream-gap%3A-kubernetes-for-the-big-leagues" aria-hidden="true">#</a></h2>
<p>Kubernetes are so hard for small groups and single users. Even <a href="https://minikube.sigs.k8s.io/docs/">miniKube</a> is way too complex! I believe everyone who touches Kubernetes, no matter newcomers or experts, is complaining about using <a href="https://yaml.org/">YAML</a> to write all workloads. I’ve seen some YAML with thousands of lines of code that still does not include custom resources. Honestly, even writing the spec YAML files will push back all tasters.</p>
<p>Since the K8s were originally designed for orchestration problems targeting large systems, we shouldn’t complain that only big companies are using them. However, the story changed when time flew by. For all K8s users, the existing system already satisfied most of their basic needs, but situations are totally different for bigger companies, they always have some new requirements and lead the development of the <a href="https://github.com/kubernetes/kubernetes">K8s OSS</a>. This turns ugly when different cloud providers start a differential competition. Due to lower-level compute resource differences, more and more optimizations for K8s are becoming secrets. Companies started to keep their own code base and only merge necessary codes upstream. This makes the system hard to understand, not even mentioned as clean and elegant.</p>
<h2 id="the-infrastructure-investment-illusion" tabindex="-1">The Infrastructure Investment Illusion <a class="header-anchor" href="#the-infrastructure-investment-illusion" aria-hidden="true">#</a></h2>
<p>Most Kubernetes users who rely on cloud providers have the same strange thinking that they can cut off investment in infrastructure. I feel the logic behind this is that users always believe that all orchestration will be done by a cloud provider, they should save time and resources by maintaining their own infrastructure. This turns into completely wrong thinking. All top customers use Kubernetes Engineers provided by the cloud provider but also keep their own tech stack and experts. Sometimes I also don’t understand why they don’t build on top of the cloud provider’s bare-metal machines, since they have all the experts and knowledge on K8s.</p>
<p>But things are completely different for some large-cap but non-tech companies. Instead of infra experts, they relied on hiring “expert K8s users.” They can definitely help with deploying workloads and doing some simple debugging. However, once a major issue impacts them, they can do nothing but wait for cloud providers to support them. To their mindset, using Kubernetes is investment-transferring, we paid slightly more rather than buy down the cloud VMs, but instead, they asked the cloud provider to support them. Sadly, even cloud providers can’t provide better support, and I observed the trend that those large-cap non-tech companies started to hire K8s developers to counter this. Overall, it still becomes costly.</p>
<hr>
<p>These are my reflections after two years. With deeper understanding, I’m sure my perspective will evolve. As I spend more time in this domain, I hope to share insights on K8s’ evolution and projects that might bridge the gap towards a more accessible Kubernetes.</p>
<p>Also, please share your thought with me.</p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/kubernetes-two-years-in-myths-and-the-complexity-conundrum/images/cover.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Use iOS shortcut scripts to summarize link with LLM]]></title>
            <link>https://pengzhan.dev/use-ios-shortcut-scripts-to-summarize-link-with-llm</link>
            <guid>https://pengzhan.dev/use-ios-shortcut-scripts-to-summarize-link-with-llm</guid>
            <pubDate>Sat, 18 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[>...]]></description>
            <content:encoded><![CDATA[<blockquote></blockquote>
<p>Minimalism is the art of knowing how much is just enough. Digital minimalism applies this idea to our personal technology. It’s the key to living a focused life in an increasingly noisy world.</p>
<blockquote></blockquote>
<p>Cal Newport. Digital Minimalism</p>
<p>What’s the most valuable idea if we have truly advanced AI technology? I bet personalized assistants will be one of the top usable use cases. Back in years, when watching White Christmas, one episode from the TV series Black Mirror, I felt so astonishingly that screenwriters were imagining the same as I do. A purely personalized assistant (no spoiler) would think as the user does, make some decisions as the user thinks, and improve as the user experience grows.</p>
<p>Unfortunately, this is still early! But LLM opens a small gap that we can peek at.</p>
<p>In my previous post, I mentioned trying to <a href="https://pengzhan.dev/generate-word-cloud-figures-with-chinese-tokenization-and-wordcloud-python-libraries/">visualize the keywords from the top news stories of the week</a>. It was a good way to quickly scan a bunch of content, but it wasn’t ideal for understanding the details of any single piece.</p>
<p>Lately, I’ve been focusing on using Large Language Models (LLMs) to summarize long articles into something more readable, especially for non-native speakers. And guess what? Today I found a cool way to do it using Apple’s App Shortcuts on iOS!</p>
<p>Here’s a demo:</p>
<p><video controls loop muted playsinline style="max-width: 100%; border-radius: 8px; margin: 1.5rem 0;" src="/posts-assets/use-ios-shortcut-scripts-to-summarize-link-with-llm/images/2024-05-13-14.28.34.mp4"></video></p>
<p><strong>Ready to give it a shot? Just install the <a href="https://apps.apple.com/us/app/chatgpt/id6448311069">ChatGPT</a> and <a href="https://apps.apple.com/us/app/claude-by-anthropic/id6473753684">Claude</a> iOS apps before using the shortcut with this link: <a href="https://www.icloud.com/shortcuts/291a21bb62ea431a937dcbd9b21218fc"></a><a href="https://www.icloud.com/shortcuts/291a21bb62ea431a937dcbd9b21218fc">https://www.icloud.com/shortcuts/291a21bb62ea431a937dcbd9b21218fc</a>.</strong></p>
<h2 id="the-whole-process" tabindex="-1">The whole process <a class="header-anchor" href="#the-whole-process" aria-hidden="true">#</a></h2>
<p>Here’s the shortcut’s entire process to achieve what you saw in the above demo (be warned, it’s a long screenshot!). To give you a better understanding before we dive in, let’s break down the whole process with some bullet points.</p>
<ul>
<li>
<p><strong>1. Grab the URL:</strong> The shortcut retrieves the URL from your device’s default share menu.</p>
</li>
<li>
<p><strong>2. Choose Summarization Language (Optional):</strong> If you’re a polyglot (someone who speaks multiple languages), you can use a pop-up menu (also called a switch statement in programming) to choose between summarizing in English or Chinese. Skip this step if you only need English summaries.</p>
</li>
<li>
<p><strong>3. Fetch Web Content:</strong> The shortcut uses the Safari engine to grab the content of the web page associated with the URL.</p>
</li>
<li>
<p><strong>4. Prepare the Summary Request:</strong> The shortcut combines the chosen summarization language (if applicable) with the fetched web content and copies the combined text to your clipboard.</p>
</li>
<li>
<p><strong>5. Select LLM Provider App:</strong> Finally, a pop-up menu allows you to choose which Large Language Model (LLM) provider app you want to use for the summarization.</p>
</li>
</ul>
<p><img src="/posts-assets/use-ios-shortcut-scripts-to-summarize-link-with-llm/images/2024-05-16-19.29.26-2-scaled.jpg" alt=""></p>
<h2 id="some-tips-for-using-scripting-blocks" tabindex="-1">Some tips for using scripting blocks <a class="header-anchor" href="#some-tips-for-using-scripting-blocks" aria-hidden="true">#</a></h2>
<p>Sadly, Apple seems to have given up on this feature. Documents are lacking, engagement is low making it hard to find answers. Here is the problem I encountered.</p>
<p><img src="/posts-assets/use-ios-shortcut-scripts-to-summarize-link-with-llm/images/2024-05-13-19.51.04-472x1024.png" alt=""></p>
<p>“The web content could not be loaded because your privacy settings do not allow this shortcut to load external web content. You can change this in the Privacy tab of the shortcut’s settings.“</p>
<p>The error message suggests fixing privacy settings, but clicking “More” (middle button in the bottom tab) reveals it’s not related. Here’s my guess: “Set Variable” likely creates a reference (alias) to the content, not actually changing its data type. The “Combine” block might not work with this “web of content” type.</p>
<p>The solution is simple (as shown in the screenshot): Use a “Text” block instead. Include the variable you want to combine, and it will convert it to plain text before combining. This creates a new text variable for the “Combine” block to work with.</p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/use-ios-shortcut-scripts-to-summarize-link-with-llm/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[How do we know if Kubelet leaks Inotify watchers]]></title>
            <link>https://pengzhan.dev/inotify-watcher-leaks-in-kubelet</link>
            <guid>https://pengzhan.dev/inotify-watcher-leaks-in-kubelet</guid>
            <pubDate>Thu, 18 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Kubelet as the node agent of Kubernetes OSS, always needs to monitor paths. Using Inotify to do so, Kubelet exposes to possibility of leaking of Inotify watchers. In recent, I obse...]]></description>
            <content:encoded><![CDATA[<p>Kubelet as the node agent of Kubernetes OSS, always needs to monitor paths. Using Inotify to do so, Kubelet exposes to possibility of leaking of Inotify watchers. In recent, I observed a case where the Kubelet was hung for enormous Inotify usage. This Post briefly discussed how I debug the process and locate the problem.</p>
<h2 id="symptom" tabindex="-1">Symptom <a class="header-anchor" href="#symptom" aria-hidden="true">#</a></h2>
<p>Recently, I faced an issue where Kubelet on a node reported error message failed to create file descriptors.</p>
<pre class="language-text"><code class="language-text">error creating file watcher: too many open files
error creating file watcher: no space left on device
</code></pre>
<p>After short checking, I found the node has <code>max_user_watches</code> of 10000, but the <code>TotalinotifyWatches</code> is beyond this value. (P. S still not sure why watcher can initiate more than cap). In order to find which process occupied the most watchers. I used following command<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> to find it out.</p>
<pre class="language-text"><code class="language-text">echo -e &quot;COUNT\tPID\tUSER\tCOMMAND&quot;; sudo find /proc/[0-9]*/fdinfo -type f 2&gt;/dev/null | sudo xargs grep ^inotify 2&gt;/dev/null | cut -d/ -f 3 | uniq -c | sort -nr | { while read -rs COUNT PID; do echo -en &quot;$COUNT\t$PID\t&quot;; ps -p $PID -o user=,command=; done}

COUNT   PID USER    COMMAND
7491    8412    root /home/kubernetes/bin/kubelet --v=2 --cloud-provide=gce --experi
2620    1   root /sbin/init
....
</code></pre>
<p>Surprisingly, Kubelet initiated more than 7000 inotify watchers. I assumed there was an inotify leakage in Kubelet.</p>
<h2 id="leakage-check" tabindex="-1">Leakage check <a class="header-anchor" href="#leakage-check" aria-hidden="true">#</a></h2>
<h3 id="clean-kubelet" tabindex="-1">Clean Kubelet <a class="header-anchor" href="#clean-kubelet" aria-hidden="true">#</a></h3>
<p>To better understand the situation, I created a clean cluster with only 1 clean node on GKE. Roughly 70 inotify watchers were there. I created a single nginx pod and the number increased by 3. Theoretically, these 3 watchers are used by Kubelet to monitor any changes on <code>rootfs</code>, <code>kube-api-access</code> and <code>PodSandbox</code>. But to verify it, we need to check more details on which inodes are monitored by Kubelet.</p>
<h3 id="check-inotify-file-descriptors" tabindex="-1">Check inotify file descriptors <a class="header-anchor" href="#check-inotify-file-descriptors" aria-hidden="true">#</a></h3>
<p>To do so, let’s take a look how to track a single inotify file descriptor. Opened processes’ <code>fdinfo</code> folder, we can examine each or them to find an inotify fd.</p>
<pre class="language-text"><code class="language-text"># Find kubelet pid
ps -aux | grep kubelet
KPID=2430

# File the an example fd
sudo ls /proc/2430/fdinfo

0 1 10 11 12 13 14 2 3 4 5 6 7 8 9

...

sudo cat /proc/2430/fdinfo/8

pos:    0
flags:  02004000
mnt_id: 15
ino:    1057
inotify wd:1 ino:3f327 sdev:800001 mask:fc6 ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:27f30300e5059ea2
</code></pre>
<p>This is very confusing, so I rely on <code>man proc</code><a href="%5Bproc(5)%5D(https://manpages.courier-mta.org/htmlman5/proc.5.html)">^man_proc</a> to understand every piece of them. In given fd, the needed information to continue sit in the last line. It’s an inotify entry represents the 1 file or folder to be monitored. And the most useful data is <code>ino:3f327</code> which represents the inode number of target file (in hexadecimal). And <code>sdev:800001</code>, which represents the ID of device where the inode sit on, and it’s also in hex.</p>
<p>Using <code>lsblk</code>, I can see there’s only 1 disk I’m using on the node, so finding the target file would be easy.</p>
<pre class="language-text"><code class="language-text"># Cast to decimal
ino=3f327
dec=&quot;$((16#${ino}))&quot;

# Find the target file
loc=&quot;debugfs -R 'ncheck ${dec}' /dev/sda1&quot;
sudo eval $loc 2&gt;/dev/null

debugfd 1.46.5 (30-Dec-2021)
Inode   Pathname
258855  /etc/srv/kubernetes/pki/ca-certificates.crt
</code></pre>
<p>Put all processes above into one single script(please see the <a href="#updated-script-to-get-inotify-watchers-initiated-by-kubelet">updated version in the appendix</a>), I can retrieve all target files, that would help to understand if there’s a real leakage. Also, I count the unique inode amount, this could also help to know which inode are monitored multiple times.</p>
<pre class="language-text"><code class="language-text">cat /dev/null | xargs grep ^inotify | cut -d &quot; &quot; -f 3 | cut -d &quot;:&quot; -f 2)
echo ${in_fds}
echo &quot;Count: $(find /proc/${PID}/fdinfo -type f 2&gt;/dev/null | xargs grep ^inotify | wc -l)&quot;

uniq_fds=$(echo &quot;${in_fds[@]}&quot; | sort | uniq)
echo ${uniq_fds}

while read -r element;
do
  count=$(echo &quot;${in_fds[@]}&quot; | grep -o &quot;${element}&quot; | wc -l)
  dec=&quot;$((16#${element}))&quot;
  loc=&quot;debugfs -R 'ncheck ${dec}' /dev/sda1&quot;
  loc=$(eval $loc 2&gt;/dev/null | tail -1 | cut -d &quot; &quot; -f 4)
  printf &quot;%-6s %-10s %-6s %s\n&quot; &quot;${element}&quot; &quot;${dec}&quot; &quot;${count}&quot; &quot;${loc}&quot;
done /dev/null | xargs grep ^inotify)
# echo ${inums_raw}
echo &quot;Count: $(find /proc/${PID}/fdinfo -type f 2&gt;/dev/null | xargs grep ^inotify | wc -l)&quot;

while read -r line;
do
        reg=&quot;ino:([0-9a-f]*) sdev:([0-9a-f]*)&quot;
        if [[ ${line} =~ $reg ]]; then
                ino=&quot;${BASH_REMATCH[1]}&quot;
                sdev=&quot;${BASH_REMATCH[2]}&quot;
                # echo $ino $sdev
        else
                echo &quot;wrong line&quot;
        fi

        sdev_in_dec=$((16#$sdev))
        minor=$((sdev_in_dec % 256))
        major=$((sdev_in_dec / 256))
        # echo &quot;${major}:${minor}&quot;

        in_fds_sdev+=(&quot;${ino}-${major}:${minor}&quot;)
done &lt;&lt;&lt; &quot;${inums_raw}&quot;

uniq_pairs=($(echo &quot;${in_fds_sdev[@]}&quot; | sort | uniq))
echo &quot;Unique target&quot; ${#uniq_pairs[@]}

printf &quot;%-10s %-10s %-6s %s\n&quot; &quot;INUM&quot; &quot;DEV&quot; &quot;COUNT&quot; &quot;TARGET&quot;
for pair in &quot;${uniq_pairs[@]}&quot;
do
        count=$(echo &quot;${in_fds_sdev[@]}&quot; | grep -o &quot;${pair}&quot; | wc -l)
        fd_hex=$(echo ${pair} | cut -d &quot;-&quot; -f 1)
        dev=$(echo ${pair} | cut -d &quot;-&quot; -f 2)
        fd_dec=&quot;$((16#${fd_hex}))&quot;

        mount_info=$(grep ${dev} /proc/$PID/mountinfo)
        if [[ -z $mount_info ]]; then
                echo &quot;Can't find mount info for&quot; $dev
        else
                tmpfs_path=$(echo $mount_info | cut -d &quot; &quot; -f 5)
                # echo $tmpfs_path
                loc=$(find ${tmpfs_path} -inum ${fd_dec})
                printf &quot;%-10s %-10s %-6s %s\n&quot; &quot;${fd_dec}&quot; &quot;${dev}&quot; &quot;${count}&quot; &quot;${loc}&quot;
        fi
done
</code></pre>
<h2 id="references" tabindex="-1">References <a class="header-anchor" href="#references" aria-hidden="true">#</a></h2>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><a href="https://stackoverflow.com/a/76712244">Fluentbit error “cannot adjust chunk size” on GKE</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/inotify-watcher-leaks-in-kubelet/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Practical debugging methods for Kubelet]]></title>
            <link>https://pengzhan.dev/debug-kubelet</link>
            <guid>https://pengzhan.dev/debug-kubelet</guid>
            <pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Kubelet, a vital component in Kubernetes, runs on each node in your cluster. It acts as the field manager, receiving instructions from the Kubernetes API server and ensuring contai...]]></description>
            <content:encoded><![CDATA[<p>Kubelet, a vital component in Kubernetes, runs on each node in your cluster. It acts as the field manager, receiving instructions from the Kubernetes API server and ensuring containerized applications run smoothly. Kubelet is responsible for downloading container images, pulling secrets, and launching pods – the basic units containing your application containers. It also monitors container health, restarts failed ones and reports their status back to the API server.</p>
<p>Due to its central role, debugging Kubelet becomes crucial when troubleshooting your Kubernetes environment. In the post, we will continually expand methods on how to debug Kubelet, especially some practices on some real-world use cases.</p>
<h2 id="debug-logs" tabindex="-1">Debug logs <a class="header-anchor" href="#debug-logs" aria-hidden="true">#</a></h2>
<p>Like all others program’s debugging, the most straightforward way for newbies and the easiest way for advanced developer is relying on logs. Similar to debugging Kubelet, bumping up verbosity to show more logs is the most intuitive approach when facing an issue. Like most components in Kubernetes, <code>kubelet</code> uses <code>klog</code> for logging and there are 10 verbosity levels(0-9).</p>
<p>TL;DR: Bumping up to level 5 would satisfy most debugging needs.</p>
<p>LevelMeaningExample0Always on (Warning, Error, Fatal)<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/kubelet.go#L757-L757">Example</a>1Default level logs when don’t want any verbosity<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/kubelet.go#L2527">Example</a>2Most important logs when major operations happen, also the default verbosity level<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/kubelet.go#L483-L483">Example</a>3Extended information<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/kubelet.go#L2176">Example</a>4Debug level<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/kubelet.go#L1731">Example</a>5Trace level<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/kubelet.go#L2821-L2821">Example</a>6Display requested resources<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/cm/cgroup_manager_linux.go#L401">Example</a>7Display HTTP request headers<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/logs/container_log_manager.go#L299">Example</a>8Display HTTP request payload<a href="https://github.com/kubernetes/kubernetes/blob/d9c54f69d4bb7ae1bb655e1a2a50297d615025b5/pkg/kubelet/prober/prober_manager.go#L192">Example</a></p>
<p>By the time, this note was written. In <code>kubelet</code> related code, level 8 was only used <code>pkg/kubelet/prober/prober_manager.go</code> and level 7 was only used in <code>pkg/kubelet/logs/container_log_manager.go</code>. 11 occurrences in level 6 were used, and all of them are not part of workload lifecycle related.</p>
<h2 id="add-trace-cide" tabindex="-1">Add trace cide <a class="header-anchor" href="#add-trace-cide" aria-hidden="true">#</a></h2>
<h2 id="kubelet-with-bpfcc-tools" tabindex="-1">Kubelet with Bpfcc-tools <a class="header-anchor" href="#kubelet-with-bpfcc-tools" aria-hidden="true">#</a></h2>
<h2 id="debugger" tabindex="-1">Debugger <a class="header-anchor" href="#debugger" aria-hidden="true">#</a></h2>
<h2 id="further-readings" tabindex="-1">Further readings <a class="header-anchor" href="#further-readings" aria-hidden="true">#</a></h2>
<p><a href="/posts/inotify-watcher-leaks-in-kubelet.html">Inotify watcher leaks in Kubelet</a></p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/debug-kubelet/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Selected Labs for CS350 courses in Binghamton University]]></title>
            <link>https://pengzhan.dev/labs-of-cs350</link>
            <guid>https://pengzhan.dev/labs-of-cs350</guid>
            <pubDate>Tue, 22 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[TL; DR. This will be a series regarding labs I gave during the spring 2022 semester....]]></description>
            <content:encoded><![CDATA[<p><strong>TL; DR</strong>. This will be a series regarding labs I gave during the spring 2022 semester.</p>
<p>The reason why I am writing this down is that it has been a week and no students have asked for the solution for the last Lab. I realize that the learning gap between students is huge, especially when a non-profit university is admitting more and more students. To help all students understand the concepts of modern OS, I decided to write this post.</p>
<p>It starts with the past lab content I have (as the skeleton), and will be amended with extra materials I think it helps.
Remember, it’s for help in learning. DON’T COPY &amp; PASTE CODE!</p>
<h2 id="lab1-introduction" tabindex="-1">Lab1-Introduction <a class="header-anchor" href="#lab1-introduction" aria-hidden="true">#</a></h2>
<h2 id="lab3-process" tabindex="-1">Lab3-Process <a class="header-anchor" href="#lab3-process" aria-hidden="true">#</a></h2>
<h2 id="lab4-ipc" tabindex="-1">Lab4-IPC <a class="header-anchor" href="#lab4-ipc" aria-hidden="true">#</a></h2>
<h2 id="lab6-7-scheduling" tabindex="-1">Lab6-7-Scheduling <a class="header-anchor" href="#lab6-7-scheduling" aria-hidden="true">#</a></h2>
<h3 id="first-user-process-in-xv6" tabindex="-1">First user process in xv6 <a class="header-anchor" href="#first-user-process-in-xv6" aria-hidden="true">#</a></h3>
<h4 id="kernel-works" tabindex="-1">Kernel works <a class="header-anchor" href="#kernel-works" aria-hidden="true">#</a></h4>
<p>In xv6, as the same as conventional Linux OS, the very first user-level process is <code>init</code>. Before <code>init</code>’s running, all the OS bootstraps happen in a highly privileged mode(kernel level).</p>
<p>Xv6’s kernel has the entry point as the main function located in the file <code>main.c</code>. The main function invokes 17 functions to set up kernel page tables, interrupt handlers, I/O devices and etc. When all kernel preparations are done, by calling the function <code>userinit()</code>, the kernel will boot up process init.</p>
<pre class="language-text"><code class="language-text">int
main(void)
{
  kinit1(end, P2V(4*1024*1024)); // phys page allocator
  kvmalloc();      // kernel page table
  mpinit();        // collect info about this machine
  lapicinit();
  seginit();       // set up segments
  cprintf(&quot;\ncpu%d: starting xv6\n\n&quot;, cpu-&gt;id);
  picinit();       // interrupt controller
  ioapicinit();    // another interrupt controller
  consoleinit();   // I/O devices &amp; their interrupts
  uartinit();      // serial port
  pinit();         // process table
  tvinit();        // trap vectors
  binit();         // buffer cache
  fileinit();      // file table
  ideinit();       // disk
  if(!ismp)
    timerinit();   // uniprocessor timer
  startothers();   // start other processors
  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
  userinit();      // first user process
  // Finish setting up this processor in mpmain.
  mpmain();
}
</code></pre>
<p>It’s tricky since that <strong>init</strong> is a user process, but kernel can’t call any user-level system calls to create it.
Why? 1. Kernel has all privileges to create a user process. So it doesn’t need to call system calls such as <em><strong>fork()</strong></em>.
And 2. All other user processes can be created by forking from its parent.
Forking including clone the whole user virtual memory layout. However, the first process has no parent to fork from.
That’s why it makes the creation of the first user process becomes so unique.</p>
<p>In <em>proc.c</em>, <em><strong>userinit()</strong></em> define there gives us the whole procedure of creating <strong>init</strong>.
Similar to the <em><strong>fork()</strong></em>, but more simple.
Process control block(structures for storing the process status) was created at the very first by calling <em><strong>allocproc()</strong></em>.
After then, by invoking <em><strong>setupkvm()</strong></em>(defined in <em>vm.c</em>), kernel memory map was setup for the process.
During setting up kernel memory map, a page size virtual memory will be assigned to the process as ready.
And later, this page size memory will be used to store instructions of <strong>init</strong>.</p>
<p>Followed by setup kernel stack for the <strong>init</strong> process, calling <em><strong>inituvm()</strong></em> will load <strong>init</strong>’s text into the page that is just being allocated.
<em><strong>inituvm()</strong></em> takes 3 arguments: a pointer to the process’s page directory (p-&gt;pgdir),
a char-type pointer declared from external which point to <strong>init</strong>’s text segment(_binary_initcode_start), and
a char-type pointer which points to an external integer as the size of the <strong>init</strong>’s text segment(_binary_initcode_size).
Simply put, it will load instructions of <strong>init</strong> into the memory.</p>
<p>So now, the problem becomes when and where did instructions for <strong>init</strong> have compiled into the kernel?</p>
<pre class="language-text"><code class="language-text">void
userinit(void)
{
  struct proc *p;
  extern char _binary_initcode_start[], _binary_initcode_size[];

  p = allocproc();
  initproc = p;
  if((p-&gt;pgdir = setupkvm()) == 0)
    panic(&quot;userinit: out of memory?&quot;);
  inituvm(p-&gt;pgdir, _binary_initcode_start, (int)_binary_initcode_size);
  p-&gt;sz = PGSIZE;
  memset(p-&gt;tf, 0, sizeof(*p-&gt;tf));
  p-&gt;tf-&gt;cs = (SEG_UCODE tf-&gt;ds = (SEG_UDATA tf-&gt;es = p-&gt;tf-&gt;ds;
  p-&gt;tf-&gt;ss = p-&gt;tf-&gt;ds;
  p-&gt;tf-&gt;eflags = FL_IF;
  p-&gt;tf-&gt;esp = PGSIZE;
  p-&gt;tf-&gt;eip = 0;  // beginning of initcode.S

  safestrcpy(p-&gt;name, &quot;initcode&quot;, sizeof(p-&gt;name));
  p-&gt;cwd = namei(&quot;/&quot;);

  p-&gt;state = RUNNABLE;
}
</code></pre>
<h4 id="where-the-user-level-code-was-integrated%3F" tabindex="-1">Where the user-level code was integrated? <a class="header-anchor" href="#where-the-user-level-code-was-integrated%3F" aria-hidden="true">#</a></h4>
<p>If you search the keyword “_binary_initcode_start” in the source code, you can’t find any references.
The clue comes from the <em>Makefile</em>.</p>
<p>In the makefile, <strong>initcode</strong> is a prerequisites to compile the kernel image.
<strong>Step 1</strong>: Before kernel was compiled, <em>initcode.S</em> was first compiled to a runnable binary <em>initcode</em>.
This binary was very odd because it was not supposed to let any other OS to run it.
<em>Initcode.s</em> was first compiled without any standard including, and generating the intermediate file <em>initcode.o</em>.</p>
<p><strong>Step 2</strong>: <em>Initcode.o</em> then linked to <em>Initcode.out</em> with two uncommon settings.
First it specify the entry of this binary file as when “start” symbol points to.
This “start” symbol was declared in the assembly code.
Second it specify a absolute address(0) for the text segments.
By doing this, text segments will be placed at the start of the binary file (except the header of the ELF)<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p>
<p><strong>Step 3</strong>: <em>Initcode.out</em> is already a minimized binary but it’s not enough.
That’s why when using <strong>objcopy</strong> to copy it to the file <em>initcode</em>, it further strip all headers and debug information<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>.
At this point, we have a minimal binary file <em>initcode</em>.
From the first byte of this file, it’s only includes runnable instructions.
And the size of the file is only 44 bytes.</p>
<pre class="language-text"><code class="language-text">initcode: initcode.S
    $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S                         # Step 1
    $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o    # Step 2
    $(OBJCOPY) -S -O binary initcode.out initcode                       # Step 3
    $(OBJDUMP) -S initcode.o &gt; initcode.asm
</code></pre>
<p>This binary later were appended to the kernel using following commands.
And during this appending, 3 symbols were generated and added to the symbol table of the <em>kernel</em><sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup>.
<strong>“_binary_initcode_start”</strong> contains the address of where the initcode segment was appended to.
<strong>“_binary_initcode_end”</strong> contains the address of where the initcode segment was ended at.
<strong>“_binary_initcode_size”</strong> is a <em>ABS</em> type symbol with value 0x2C(45) that specify the size of the initcode segment is 45 bytes.</p>
<pre class="language-text"><code class="language-text">kernel: $(OBJS) entry.o entryother initcode kernel.ld
    $(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) -b binary initcode entryother #  kernel.asm
    $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' &gt; kernel.sym
</code></pre>
<p><strong>In short summary</strong>,
using objdump, we can verify that source code <em>initcode.S</em> has been compiled and loaded into the kernel.
Also the segment of initcode’s instructions was located by the pointer “_binary_initcode_start”.
That’s explain when calling <em><strong>inituvm(p-&gt;pgdir, _binary_initcode_start, (int)_binary_initcode_size);</strong></em>,
functionalities implemented in initcode.S will be loaded into the runtime of the first process within xv6.</p>
<pre class="language-text"><code class="language-text"># Header of the file kernel
kernel:     file format elf32-i386
kernel
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0010000c

Program Header:
    LOAD off    0x00001000 vaddr 0x80100000 paddr 0x00100000 align 2**12
         filesz 0x00008c6a memsz 0x00008c6a flags r-x
...
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00008586  80100000  00100000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
...
SYMBOL TABLE:
...
8010b50c g       .data  00000000 _binary_initcode_end
...
8010b4e0 g       .data  00000000 _binary_initcode_start
...
0000002c g       *ABS*  00000000 _binary_initcode_size
...
</code></pre>
<h4 id="user-level-code" tabindex="-1">User-level code <a class="header-anchor" href="#user-level-code" aria-hidden="true">#</a></h4>
<p>Take a look of content in the <em>initcode.S</em>, you will find the code can explain itself well.
There are no other jobs but just calling system call <strong>exec</strong> to run a user-level binary <strong>“init”</strong>.</p>
<p><em>Initcode.S</em>:</p>
<pre class="language-text"><code class="language-text"># Initial process execs /init.

#include &quot;syscall.h&quot;
#include &quot;traps.h&quot;

# exec(init, argv)
.globl start
start:
  pushl $argv
  pushl $init
  pushl $0  // where caller pc would be
  movl $SYS_exec, %eax
  int $T_SYSCALL

# for(;;) exit();
exit:
  movl $SYS_exit, %eax
  int $T_SYSCALL
  jmp exit

# char init[] = &quot;/init\0&quot;;
init:
  .string &quot;/init\0&quot;

# char *argv[] = { init, 0 };
.p2align 2
argv:
  .long init
  .long 0
</code></pre>
<p>The <strong>“init”</strong> mentioned above is not a pure user-level binary executable that compiled from the source code <em>init.c</em>.
Within <em>init.c</em>, a file named <em>console</em> will be created at the runtime for saving standard outputs and errors.
Then it will forked a child process(the second user process), and let it run program <strong>“sh”</strong>.</p>
<p><strong>“sh”</strong> is the xv6’s default shell, a user-level program that generated from source <em>sh.c</em>.
After the shell boots up, you can interactive with the xv6.
This’s how first process (and second process) was started in the xv6.</p>
<p><em>init.c</em>:</p>
<pre class="language-text"><code class="language-text">// init: The initial user-level program

#include &quot;types.h&quot;
#include &quot;stat.h&quot;
#include &quot;user.h&quot;
#include &quot;fcntl.h&quot;

char *argv[] = { &quot;sh&quot;, 0 };

int
main(void)
{
  int pid, wpid;

  if(open(&quot;console&quot;, O_RDWR) = 0 &amp;&amp; wpid != pid)
      printf(1, &quot;zombie!\n&quot;);
  }
}
</code></pre>
<h3 id="xv6%E2%80%99s-round-robin-schduler" tabindex="-1">Xv6’s round robin schduler <a class="header-anchor" href="#xv6%E2%80%99s-round-robin-schduler" aria-hidden="true">#</a></h3>
<p>The Scheduler is the core of an operating system.
With the scheduling of processes, the kernel can achieve near-real-time execution of multiple workloads.
The scheduling problem is also an active aspect of computer science research.
You can’t have one algorithm to fit all scenarios.</p>
<p>Xv6 by default has a round-robin scheduler.
It’s controlled using two-level for-loops, where the top-level for-loop is an endless loop that will keep the scheduler busy running.
The second-level nested for-loop will iterate a data structure named Ptable where all control information for processes is stored.
Information including pid, process name, etc. is stored in a structure called proc. Ptable is an array of processes.
Every runnable process in the Ptable will run strictly 1 time tick until the for-loop reached the last process in the Ptable.
Then it will loop back to the top-level for-loop for the next iteration of processes.</p>
<pre class="language-text"><code class="language-text">// In file proc.c
struct {
  struct spinlock lock;
  struct proc proc[NPROC];
} ptable;

// In file proc.h
struct proc {
  uint sz;                     // Size of process memory (bytes)
  pde_t* pgdir;                // Page table
  char *kstack;                // Bottom of kernel stack for this process
  enum procstate state;        // Process state
  int pid;                     // Process ID
  struct proc *parent;         // Parent process
  struct trapframe *tf;        // Trap frame for current syscall
  struct context *context;     // swtch() here to run process
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};
</code></pre>
<pre class="language-text"><code class="language-text">// In file proc.c
void
scheduler(void)
{
  struct proc *p;

  for(;;){
    // Enable interrupts on this processor.
    sti();

    // Loop over process table looking for process to run.
    acquire(&amp;ptable.lock);
    for(p = ptable.proc; p state != RUNNABLE)
        continue;

      // Switch to chosen process.  It is the process's job
      // to release ptable.lock and then reacquire it
      // before jumping back to us.
      proc = p;
      switchuvm(p);
      p-&gt;state = RUNNING;
      swtch(&amp;cpu-&gt;scheduler, proc-&gt;context);
      switchkvm();

      // Process is done running for now.
      // It should have changed its p-&gt;state before coming back.
      proc = 0;
    }
    release(&amp;ptable.lock);

  }
}
</code></pre>
<p>It’s not hard to understand why this logic makes a round-robin manner.
This is very important to understand how to pick a process to run because scheduling is about always picking the appropriate process to achieve higher performance.</p>
<p>You can always come up with some new ideas for designing a good scheduler policy.
Understanding how to switch from one process to another is equivalently important.</p>
<p>Once the process for the next time tick is selected.
It’s time to switch from the running scheduler to the selected process. Wait for a second, there are two questions we haven’t answered.</p>
<ul>
<li>
<p>What is the running scheduler?</p>
</li>
<li>
<p>How did the last running process stop running and give the CPU back to the scheduler?</p>
</li>
</ul>
<h3 id="lab" tabindex="-1">Lab <a class="header-anchor" href="#lab" aria-hidden="true">#</a></h3>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><a href="https://linux.die.net/man/1/ld">ld(1) - Linux man page</a> <a href="#fnref1" class="footnote-backref">↩︎</a> <a href="#fnref1:1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p><a href="https://sourceware.org/binutils/docs/binutils/objcopy.html">3 objcopy - binutils mannual</a> <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/labs-of-cs350/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[EDDL: How do we train neural networks on limited edge devices - PART 2]]></title>
            <link>https://pengzhan.dev/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-2</link>
            <guid>https://pengzhan.dev/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-2</guid>
            <pubDate>Sun, 31 Oct 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[In the last post, part1, our idea of distributed learning on edge environment was generally addressed.
I introduced the reason why edge distributed learning is needed and what impr...]]></description>
            <content:encoded><![CDATA[<p>In the last post, part1, our idea of distributed learning on edge environment was generally addressed.
I introduced the reason why edge distributed learning is needed and what improvements it can achieve.
In this post, I will talk about our motivation study and how our framework works.</p>
<h2 id="how-does-data-support-us-training-on-edge%3F" tabindex="-1">How does data support us training on edge? <a class="header-anchor" href="#how-does-data-support-us-training-on-edge%3F" aria-hidden="true">#</a></h2>
<p>Before designing and implementing our framework, we first need confirmation that training on edge resource-limited devices is worthwhile.
We were using a malware detection neural network to show why a small, customized neural network is better.</p>
<p>We collected 32000+ mobile apps feature as global data.
With these data records, we trained a multilayer perceptron called “PerNet” to determine whether a given feature belongs to a benign or malware app.
We called this <strong>detection</strong>.
As well, PerNet can also classify malware apps into different types of attacks.
We called this <strong>classification</strong>.
The global model can achieve 93% above recall rate and 96.93% above accuracy.</p>
<p>With all these data, we selected two community app usage sub-dataset for local model generations.</p>
<ul>
<li></li>
</ul>
<p>Large categories (Scenario 1)
We chose the 5 largest categories of apps, including entertainment, tools, brain&amp;Puzzle, Lifestyle, and Education, as well as the 5 largest malware categories.
All together, 12000+ apps were included in this sub-dataset, almost 50 to 50 between benign and malware.</p>
<ul>
<li></li>
</ul>
<p>Campus-community categories (Scenario 2)
We chose the 5 most downloaded categories from college students as benign groups, as well as a similar amount of 5 malware categories.
To ensure that malware apps are included in 5 benign categories, we also considered synthesizing some other malware apps within categories of 5 most downloaded(benign) categories.</p>
<p>With these two types of sub-dataset, we used the same PerNet to generate multiple local models.
Under each scenarios experiment, we compared global and local models on the preserved test dataset.
In all classification performances, local beat global in every scenario.
In detection performances, local also share the same accuracy as global does.</p>
<p><img src="/posts-assets/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-2/images/t.3_inference_result.png" alt=""></p>
<p>In summary, local models were trained on special occasions.
Under the same circumstance, a global model can achieve no better accuracy than local models.
The reason why local is better might be because of overfitting.
I believe this issue also be considered in the machine learning communities that they brought <a href="https://en.wikipedia.org/wiki/Transfer_learning">transfer learning</a>,
a technique to optimize global models to special scenarios but performing more training to a global model once it’s shipped to local.</p>
<h2 id="design-and-implementation" tabindex="-1">Design and Implementation <a class="header-anchor" href="#design-and-implementation" aria-hidden="true">#</a></h2>
<h3 id="overall-design" tabindex="-1">Overall design <a class="header-anchor" href="#overall-design" aria-hidden="true">#</a></h3>
<p>The basic EDDL distributed training setup consists of 3 parts.
<strong>EDDL training cluster</strong>, a device cluster that consists of edge or mobile devices that are participating in training.
<strong>EDDL manager</strong>, the initial driver program that works as collect training data, relay data to training devices and initial training clusters.
<strong>Training data entry (TDE)</strong>, a data storage for all training data.</p>
<h3 id="dynamic-training-data-distribution" tabindex="-1">Dynamic training data distribution <a class="header-anchor" href="#dynamic-training-data-distribution" aria-hidden="true">#</a></h3>
<p>Existing distributed DNN training solutions usually statically partition training data among workers.
It can be a problem when the training node joins and exits.
We designed our framework that can dynamically distribute training data during learning.
Before every training batch started, a batch of TDE will be sent to devices.</p>
<p>In our experiments, we found that by applying this design, overall training time was shortened by doing.
Especially in large amount devices cases, this optimization can be 50% less than statically divided.</p>
<h3 id="scaling-up-cluster-size" tabindex="-1">Scaling up cluster size <a class="header-anchor" href="#scaling-up-cluster-size" aria-hidden="true">#</a></h3>
<p>Our framework was designed to have both sync and async parameter aggregation.
Asynchronous aggregation can allow a high outcome of training batch but with a sacrifice or converge time.
Synchronous aggregation allows a quick converge time in epochs, however can’t ensure performance when there’s a struggler worker.</p>
<p>As showed in experiments, we chose sync as default because the converging time is dominant in overall training time.
But, we also considered the possibilities of that async with more workers can achieve similar overall training time.</p>
<p>We introduced a formula to determine whether adding more training nodes can help or not.
Here we used bandwidth usage coefficient (BUC) as</p>
<p>$$ BUC = \dfrac{n}{T_{sync}} $$</p>
<p>In this formula, $$n$$ is the number of devices, and $latex T_{sync}$ is the transmission time of parameters.
With an increasing number of workers, n increase linearly but transmission time does not.
When $$BUC$$ increases, the cluster can speed up training time by adding workers.
Otherwise, adding more workers won’t help with overall training time.</p>
<h3 id="adaptive-leader-role-splitting" tabindex="-1">Adaptive leader role splitting <a class="header-anchor" href="#adaptive-leader-role-splitting" aria-hidden="true">#</a></h3>
<p>The idea of role splitting is simple that a device can work as a worker as well leader.
The advantage of doing this is straightforward that we can transfer 1 less parameter and training time will be shortened.</p>
<p>However, in our current settings, it can’t perform much better help since only 1 leader role is in a cluster.
We can benefit from this in our future works.</p>
<h3 id="overall-architecture" tabindex="-1">Overall architecture <a class="header-anchor" href="#overall-architecture" aria-hidden="true">#</a></h3>
<p><img src="/posts-assets/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-2/images/f.5_Impl_leader_worker.png" alt=""></p>
<p>Details were given in the image.</p>
<h3 id="prototype-hardware-and-software" tabindex="-1">Prototype hardware and software <a class="header-anchor" href="#prototype-hardware-and-software" aria-hidden="true">#</a></h3>
<p>EDDL was designed to be run on two single-board computer embedded platforms.
One such platform is <a href="https://www.hardkernel.com/shop/odroid-xu4-special-price/">ODROID-XU4</a>, which is equipped with a 2.1/1.4 GHz 32-bit ARM processor and 2GB memory.
The other platform is the <a href="https://www.raspberrypi.com/products/raspberry-pi-3-model-b/">Raspberry Pi 3 Model B board</a>, which comes with an ARM 1.2 GHz 64-bit quad-core processor and 1GB memory.</p>
<p>The operating system running on the above platforms is Ubuntu 18.04 with Linux kernel 4.14.
We used <a href="http://dlib.net/">Dlib</a>, a C++ library that provides implementations for a wide range of machine learning algorithms.
We chose the Dlib library because it is written in C/C++, and can be easily and natively used in embedded devices.</p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-2/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[EDDL: How do we train neural networks on limited edge devices - PART 1]]></title>
            <link>https://pengzhan.dev/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-1</link>
            <guid>https://pengzhan.dev/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-1</guid>
            <pubDate>Wed, 13 Oct 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This post introduces our previous milestone in the project named "Edge Trainer", as the paper "EDDL: A Distributed Deep Learning System for Resource-limited Edge Computing Environm...]]></description>
            <content:encoded><![CDATA[<p>This post introduces our previous milestone in the project named “Edge Trainer”, as the paper “EDDL: A Distributed Deep Learning System for Resource-limited Edge Computing Environment.” was published. As the first part of the introductions, I focus only on the motivation and summary of our works. More details on design and implementation can be found in late posts.</p>
<h2 id="why-do-we-need-training-on-edge%3F" tabindex="-1">Why do we need training on edge? <a class="header-anchor" href="#why-do-we-need-training-on-edge%3F" aria-hidden="true">#</a></h2>
<p>Cloud is not trustworthy anymore. More and more facts support that breaches on the cloud happen more frequently than before. Nowadays, with more generated personal sensitive data being uploaded to the cloud center, tech companies know better someone than the user. Researchers, no matter whether in the industry or academia, are working in a way where learning from users’ data but also keeping raw sensitive data under users’ control. Many publications have already shown the feasibility of only sharing the after-trained model instead of raw data. One recent popular study on this is Google’s <a href="https://ai.googleblog.com/2017/04/federated-learning-collaborative.html">federated learning</a>. During investigating this problem, we found that letting end-user train their data is safe, but sacrifice efficiency. Since one end device has limited resources, training time and power consumption can be disappointing. We believe there must be leverage between privacy and efficiency in some target scenarios.</p>
<p>Fortunately, we observed that users who belongs to the same campus, plant, firm, and community always share similar interests.</p>
<p>Therefore, these co-located users have similar demands in using AI-involved routines. Also, co-located users are easily targeted by the same type of threats, such as ransomware to financial practitioners. Think about this, sending features of a new malware app to cloud services to train neural networks used by antivirus programs. This process may take a long time and a small number of samples may not be recognized by the global neural networks model. A customized local model trained and deployed on the edge can successfully counter the problem. With edge training as a supplement to the cloud, training can achieve better response time and make the whole system more flexible.</p>
<h2 id="why-training-on-edge-is-hard%3F" tabindex="-1">Why training on edge is hard? <a class="header-anchor" href="#why-training-on-edge-is-hard%3F" aria-hidden="true">#</a></h2>
<p>Since all co-located users’ devices can be used for edge training, issues and challenges occur when deploying this distributed system. The first challenge is <strong>struggling workers</strong>. Training devices are heterogeneous, from limited IoT cameras to high-end media centers with powerful GPUs.They are not designed to do machine learning.
So, a good edge-based distributed learning framework must be able to handle a variety of speeds in training tasks.</p>
<p>The second challenge is how to <strong>scale up</strong> clusters. On a campus, thousands and more devices may contribute computing resources to the same training tasks.
However, these devices may be located far in physical or network topology. The question of how can we well use them well, without struggling with endless transmission time remains a challenge.</p>
<p>The third issue is the frequent <strong>joining and exiting</strong> of devices. We can’t rely on each device to faithfully work on training tasks rather than their original workload. Smartly scheduled work balance and handle join/exit issues also need under considered.</p>
<h2 id="our-proposal" tabindex="-1">Our proposal <a class="header-anchor" href="#our-proposal" aria-hidden="true">#</a></h2>
<ul>
<li></li>
</ul>
<p>Dynamic training data distribution and runtime profiler</p>
<p>We design a dynamic training data distribution mechanism that helps both the first and the third challenges. Preprocessing data can be transmitted without leakage of raw and sensitive information. This can help struggling workers who can train small batches in order to upload parameters with a similar training time. Also, for extremely slow devices, join and exit of devices cases, dynamic data distribution, and profiler can help with keeping global training parameters from pollution and staleness.</p>
<p>To counter heterogeneity, more approaches were applied in our later research. More details were introduced to the runtime profiler in the later works.</p>
<ul>
<li></li>
</ul>
<p>Asynchronous and synchronous aggregation enabled</p>
<p>In our findings, asynchronous and synchronous parameter update have their pros and cons. Keeping sync all the time leads to struggling worker issues unsolvable. However, async’s harm to accuracy and convergence time also needs attention. To carefully choose between these two update policies at the runtime is what we proposed to make use of their own advantages.</p>
<ul>
<li></li>
</ul>
<p>Leader role splitting</p>
<p>The idea is to let worker devices with higher bandwidth take leader roles during training. Parameter updating does not require much computation but only needs a great of bandwidth. Devices with sufficient bandwidth can also work as virtual leader devices. This approach helps minimize the physical devices we use and more leaders can further scale up workers’ limits.</p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/eddl-how-do-we-train-neural-networks-on-limited-edge-devices-part-1/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Visualize as Word Cloud for Chinese keywords]]></title>
            <link>https://pengzhan.dev/generate-word-cloud-figures-with-chinese-tokenization-and-wordcloud-python-libraries</link>
            <guid>https://pengzhan.dev/generate-word-cloud-figures-with-chinese-tokenization-and-wordcloud-python-libraries</guid>
            <pubDate>Tue, 15 Sep 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Data visualization is amazing. For example, by looking at the following word cloud figure full of Chinese keywords, you can easily learn what happened in last week's trend news. Le...]]></description>
            <content:encoded><![CDATA[<p>Data visualization is amazing. For example, by looking at the following word cloud figure full of Chinese keywords, you can easily learn what happened in last week’s trend news. Let’s generate a word cloud like this.</p>
<p><img src="/posts-assets/generate-word-cloud-figures-with-chinese-tokenization-and-wordcloud-python-libraries/images/2020-06-28.png" alt=""></p>
<p>Don’t understand the language is not a big deal. If your written language is based on the Latin alphabet(or other languages have space between words), skip the section tokenization.</p>
<h2 id="why-i-want-to-have-a-word-cloud" tabindex="-1">Why I want to have a Word Cloud <a class="header-anchor" href="#why-i-want-to-have-a-word-cloud" aria-hidden="true">#</a></h2>
<p>Recently, I set up a web-based RSS client for retrieving and organizing everyday news. I used <a href="https://tt-rss.org/">TinyTinyRSS</a>, or TT-Rss, a popular RSS client that is friendly to docker. Thanks to developer HenryQW, a well-written Nginx-based docker configuration is already available in the docker hub. With more feeds being added, I found some feeds do not need to be checked daily. Thus I was thinking of creating a script to automatically list all keywords that appear in a last period and generate a heat map kind of figure of it.</p>
<p>Before you go further, I’ll tell you all my settings to give readers a general overview.</p>
<p>My first step is to read all text-based information from TTRSS’s PostgreSQL database. With information, I used a Chinese-NLP library, <a href="https://github.com/fxsjy/jieba">Jieba</a>, to extract all keywords with the frequency of their occurrence. By using WordCloud, a Python library, a word cloud figure is generated and presented. More details will be discussed in later sections.</p>
<h2 id="keyword-readiness%3A-get-rss-feeds%E2%80%99-text" tabindex="-1">Keyword readiness: Get RSS feeds’ text <a class="header-anchor" href="#keyword-readiness%3A-get-rss-feeds%E2%80%99-text" aria-hidden="true">#</a></h2>
<p>My first thought is generating a keyword heat map for economic news of the last week. Since this blog post is more skewed to Chinese tokenization and draws the word cloud figure, I will leave my code here just in case. The SQL connector I used is psycopg2, an easy-to-use PostgreSQL library.</p>
<pre class="language-text"><code class="language-text">def __init__(self):
    self.dbe = psycopg2.connect(
        host=DB_HOST, port=DB_PORT, database=DB_NAME, user=DB_USER, password=DB_PASS)

def get_1w_of_feed_byid(self, id=1) -&gt; list:
    cur = self.dbe.cursor()
    cur.execute('SELECT content FROM public.ttrss_entries \
        where date_updated &gt; now() - interval \'1 week\' AND id in ( \
        select int_id from DB_TABLE_NAME \
        where feed_id=' + str(id) + ' \
        ) \
        ORDER BY id ASC '
        )
    rows = cur.fetchall()
    return rows
</code></pre>
<p>Most arguments are intuitive and easy to understand. The only exception is the argument of the function <code>get_1w_of_feed_byid</code>. This <code>id</code> is the feed index of my subscriptions.</p>
<h2 id="tokenize-with-frequency" tabindex="-1">Tokenize with frequency <a class="header-anchor" href="#tokenize-with-frequency" aria-hidden="true">#</a></h2>
<p>Two popular tokenization libraries were used, and I chose <a href="https://github.com/fxsjy/jieba">Jieba</a>after a few comparisons. Before cutting the sentence, we first need to remove all punctuation marks.</p>
<pre class="language-text"><code class="language-text">def remove_biaodian(text: str) -&gt; str:
    punct = set(u''':!),.:;?]}¢'&quot;、。〉》」』】〕〗〞︰︱︳﹐､﹒
                ﹔﹕﹖﹗﹚﹜﹞！），．：；？｜｝︴︶︸︺︼︾﹀﹂﹄﹏､～￠
                々‖•·ˇˉ―--′’”([{£¥'&quot;‵〈《「『【〔〖（［｛￡￥〝︵︷︹︻
                ︽︿﹁﹃﹙﹛﹝（｛“‘-—_…''')
    ret = &quot;&quot;
    for x in text:
        if x in punct:
            ret += ''
        else:
            ret += x
    return ret
</code></pre>
<p>After we have an all-character string, we can call Jieba. By using the function <code>jieba.posseg.cut</code> with or without a paddle, we can have a word list and their “part of speech”. As you can see in the following code, I also did two more works.</p>
<p>First, in the if statement, I only kept all nouns with some categories. Category abbreviations such as “nr” and “ns” represent different “parts of speech”, I attached the categories I used in the following table. For more details, you can find this <a href="https://github.com/fxsjy/jieba">link</a>.</p>
<p>The second work is only keeping words with lengths longer than 2 characters. In Chinese, there’s no space between words such as in Latin writing systems. Since then, some single-character words such as conjunction words are easy to be misrecognized as specialty nouns. This misrecognition will cause more single-character being regarded as specialty nouns. I am not able to improve the NLP method, so I used an easy way to fix this by removing any words less than 2 characters.</p>
<pre class="language-text"><code class="language-text">import jieba.posseg as pseg

def get_noun_jieba(self, content: str) -&gt; list:
    content = remove_biaodian(content)
    words = pseg.cut(content)   # Invoking jieba.posseg.cut function 

    ret = []
    for word, flag in words:
        # print(word, flag)
        if flag in ['nr', 'ns', 'nt', 'nw', 'nz', 'PER', 'ORG', 'x']:   # LOC
            ret.append(word)
    return [remove_biaodian(i) for i in ret if i.strip() != &quot;&quot; and len(remove_biaodian(i.strip())) &gt;= 2]
</code></pre>
<ul>
<li>Word category names and abbreviations</li>
</ul>
<p>AbbreviationCategory name/ Part of speechnrPeople name nounnsLocation name nounntOrganization name nounnwArts work nounnzOther nounPERPeople name nounORGLocation name nounxNon-morpheme word</p>
<p>With all words extracted, we can easily calculate their frequencies. After this, we can using the following line of code to print a sorted result to verify correctness.</p>
<pre class="language-text"><code class="language-text">noun = seg.get_noun_jieba(test_content)
# ... Calculate frequency of above word list ...
print(sorted(a_dict.items(), key=lambda x: x[1]))
</code></pre>
<h2 id="draw-word-cloud" tabindex="-1">Draw word cloud <a class="header-anchor" href="#draw-word-cloud" aria-hidden="true">#</a></h2>
<p>With a keyword and frequency dictionary(data structure), we can just call built-in functions from <code>Wordcloud</code> library to generate the figure.</p>
<p>First, we need to initialize an instance of <code>wordcloud</code> class. As you can see in my code, I set it with 6 parameters. Width and Height of the canvas, the maximum amount of words used to generate the figure, the font of words, the background color, and the margin between any two words.</p>
<p>After having the instance, we call the function <code>generate_from_frequencies</code> and pass the keyword dictionary to it. The return value of this function is a bitmap image, which we can use <a href="https://matplotlib.org/">Matplotlib</a>to plot it to your screen.</p>
<p>I tested my plot on ubuntu-subsystem on Windows 10, unfortunately, Matplotlib under subsystem depends on the x11 window manager, and it’s not default available on Windows. We need to install an x11 manager to support this. <a href="https://sourceforge.net/projects/xming/">Xming</a> is the one I used.</p>
<pre class="language-text"><code class="language-text">from wordcloud import WordCloud
import matplotlib.pyplot as plt

font_path = &quot;./font/haipai.ttf&quot;
output_path = &quot;./font/out.png&quot;

def show_figure_with_frequency(keywords: dict):
    wc = WordCloud(width=828, height=1792, max_words=200, font_path=font_path,
                   background_color=&quot;white&quot;, margin=1).generate_from_frequencies(keywords)
    plt.imshow(wc)
    plt.axis('off')
    plt.show()
</code></pre>
<p>If everything work fine, a word cloud figure will show up in a new window. My version looks like this.</p>
<p><img src="/posts-assets/generate-word-cloud-figures-with-chinese-tokenization-and-wordcloud-python-libraries/images/2020-06-28.png" alt="This generated word cloud figure reflects the most popular economy news keyword in the week started 06-28-2020." title="This generated word cloud figure reflects the most popular economy news keyword in the week started 06-28-2020."></p>
<p>The two largest words in the figure are “新冠” and “新冠病毒”, both meaning “Covid-19” (This figure was in the week of the second covid spur in Beijing, China). The size of the image fits my phone screen and I can use an app to automatically sync it to my phone’s wallpaper. However, in this image, too many location nouns are presented. This will be something I can make progress on in the future.</p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/generate-word-cloud-figures-with-chinese-tokenization-and-wordcloud-python-libraries/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Introduction to Xv6: how system call works]]></title>
            <link>https://pengzhan.dev/xv6-introduction</link>
            <guid>https://pengzhan.dev/xv6-introduction</guid>
            <pubDate>Fri, 28 Jul 2017 00:00:00 GMT</pubDate>
            <description><![CDATA[In this post, you will learn a few basic concepts of xv6. The learning path will be closely coupled with the first project assignment I gave when I assisted in teaching OS classes....]]></description>
            <content:encoded><![CDATA[<p>In this post, you will learn a few basic concepts of xv6. The learning path will be closely coupled with the first project assignment I gave when I assisted in teaching OS classes.
Understanding system calls and knowing how to implement a simple one will be covered in the first half.
In the second half of this post, I will discuss a little bit more on how to debug xv6 using gdb.</p>
<h2 id="xv6-system-call" tabindex="-1">Xv6 System call <a class="header-anchor" href="#xv6-system-call" aria-hidden="true">#</a></h2>
<p>To invoke a system call, we have to first define a user-mode function to be the interface of the kernel instruction in file <em>user.h</em>.</p>
<pre class="language-text"><code class="language-text">void function (void);
</code></pre>
<p>This interface-like function will then pass the function name, in this case function, to <em>usys.S</em>. When using user mode function in programs, <em>usys.S</em> will generate a reference to SYS_function and push system call number of this function into %eax. After that, system can know from <em>syscall.c</em> and determining whether this system call is available. We must define same name system function and add it into <em>syscall.h</em> and <em>syscall.c</em>.</p>
<pre class="language-text"><code class="language-text">#define SYS_function ##  // ## is the system call number
[SYS_function]  sys_function // real system function name
extern int sys_function(void); // real system function declaration
</code></pre>
<p>After adding these sentences to syscall files, we can implement real function in specific place where you want to make the function works well.</p>
<p>Sometimes, we need to pass variables among system calls. In this case, variables’ values are not necessary and even can’t be pass directly into system_function. When invoke a system call function, all variables of this system call will be pushed into current process’ stack. In file <em>syscall.c</em>, multiple functions are provided to get these variables from the process. I won’t waste time on explaining how to use these functions especially when elegant and detailed comments were written in source codes. However, I will explain concepts and how process organized and works in xv6 in future articles.</p>
<h2 id="debug-xv6-with-gdb" tabindex="-1">Debug xv6 with gdb <a class="header-anchor" href="#debug-xv6-with-gdb" aria-hidden="true">#</a></h2>
<p>Please make sure that you have used gdb before.
If you never used gdb, you may write a simple 50-100 lines c code and practice how to use gdb first.</p>
<ul>
<li>
<p><a href="https://sourceware.org/gdb/current/onlinedocs/gdb/">GDB Manual</a></p>
</li>
<li>
<p><a href="https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf">GDB cheatsheet (pdf)</a></p>
</li>
</ul>
<p>To make sure xv6 gdb enabled, please check if <em>.gdbinit.tmpl</em> file exist.
This file is used for generate <em>.gdbinit</em> file which you can late consider it as a configuration for gdb.</p>
<p>Before running the xv6 instance in QEMU, one more thing you need to know is that using gdb to debug xv6 must be attached remotely.
This is because xv6 was running within QEMU, and emulator is virtually gapped from the host device.
Later when you start debugging, QEMU will open a gdb server to let gdb client connect to.</p>
<p>Once you want to start, use following command to compile and run xv6</p>
<pre class="language-text"><code class="language-text">$ make qemu-nox-gdb
*** Now run 'gdb'.
qemu-system-i386 -nographic -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp 2 7
</code></pre>
<p>At this moment, it feels xv6 was stuck, this is because QEMU is ready to be connected by the gdb client.
You may use the <em>.gdbinit</em> to automatically finish this remote connection by simply typing in the following command in another terminal.</p>
<pre class="language-text"><code class="language-text">$ gdb -x .gdbinit
GNU gdb (Debian 8.2.1-2+b3) 8.2.1

...

The target architecture is assumed to be i8086
[f000:fff0]    0xffff0: ljmp   $0x3630,$0xf000e05b
0x0000fff0 in ?? ()
+ symbol-file kernel
warning: A handler for the OS ABI &quot;GNU/Linux&quot; is not built into this configuration
of GDB.  Attempting to continue with the default i8086 settings.

(gdb)
</code></pre>
<p>Now within this gdb client shell, type ‘c’ to continue the xv6, and you will see xv6 start execution in the first terminal.</p>
<p>At this moment, you may add breakpoints to your code to see if your code is correctly implemented or not.</p>
<p><strong>One more thing</strong>, if you open <em>.gdbinit</em> file, you’ll find that it by default connect to a localhost target.
If you are working on some other environment where the target and the client were not placed in the same device, change the localhost to IP address correspondingly.
Using ssh may connect to different physical devices under the same domain name, this is because load balancers were used. To check the IP address, search command <em>IP</em>.</p>
<pre class="language-text"><code class="language-text">target remote localhost:28467
# target remote [ip-addr]:28467
</code></pre>
<h2 id="further-readings" tabindex="-1">Further readings <a class="header-anchor" href="#further-readings" aria-hidden="true">#</a></h2>
<p><a href="https://pengzhan.dev/labs-of-cs350/#lab6-7-scheduling">Post regarding how Xv6 scheduler works from kernel bootstrap to first user process</a></p>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/xv6-introduction/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Some of my previews experiment works: 2016]]></title>
            <link>https://pengzhan.dev/some-of-my-previews-experiment-works-2016</link>
            <guid>https://pengzhan.dev/some-of-my-previews-experiment-works-2016</guid>
            <pubDate>Fri, 28 Oct 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[This blog contains only some basic record of my works. For some details, I will write a unique blog just for some specific topics....]]></description>
            <content:encoded><![CDATA[<p>This blog contains only some basic record of my works. For some details, I will write a unique blog just for some specific topics.</p>
<h2 id="2016-10" tabindex="-1">2016-10 <a class="header-anchor" href="#2016-10" aria-hidden="true">#</a></h2>
<h3 id="time-experiment-of-rsync" tabindex="-1">Time Experiment of rsync <a class="header-anchor" href="#time-experiment-of-rsync" aria-hidden="true">#</a></h3>
<p>Patch is based on rsync with version 3.1.2. [<a href="https://download.samba.org/pub/rsync/rsync-3.1.2.tar.gz">Rsync</a>|<a href="/static/2016-10/rsync/rsync-3.1.2-time.patch">Patch</a>]</p>
<h4 id="how-to-collect-data" tabindex="-1">How to collect data <a class="header-anchor" href="#how-to-collect-data" aria-hidden="true">#</a></h4>
<p>Basically, everything of transmission time and computation time will be output with overall time will be printed on the console.
But we also need some bash script to collect data through different size of random size and with different modification through them.</p>
<ul>
<li>
<p>Start from 8K to 64M, modify at beginning, [<a href="/static/2016-10/rsync/small2Big_change_at_begin.sh">Bash script</a>]</p>
</li>
<li>
<p>Start from 8K to 64M, modify at last, [<a href="/static/2016-10/rsync/small2Big_change_at_last.sh">Bash script</a>]</p>
</li>
<li>
<p>Start from 8K to 64M, modify at random place with a (slow) python script, [<a href="/static/2016-10/rsync/small2Big_change_at_anyplace.sh">Bash script</a>|<a href="/static/2016-10/rsync/addbyte.py">Python program</a>]</p>
</li>
</ul>
<h3 id="time-experiment-of-seafile" tabindex="-1">Time Experiment of seafile <a class="header-anchor" href="#time-experiment-of-seafile" aria-hidden="true">#</a></h3>
<p>Patch is based on seafile 5.1.4. You can find the release from <a href="https://github.com/haiwen/seafile/releases">seafile official repo</a>. You may follow official compile instructions from <a href="https://manual.seafile.com/build_seafile/linux.html">here</a>. [<a href="">Patch <strong>no longer avaiable, new version at following sections</strong></a>]</p>
<h4 id="how-to-collect-data-1" tabindex="-1">How to collect data <a class="header-anchor" href="#how-to-collect-data-1" aria-hidden="true">#</a></h4>
<p>We also need everything be done using scripting. But this time I only design added some distance between two increasing files’ sizes.</p>
<ul>
<li>
<p>Start from 8K to 16M, 4 times increasing, modify at beginning/ at 1024 different places with python script. [<a href="/static/2016-11/seafile/trans.sh">Bash Script</a>|<a href="/static/2016-11/seafile/addbyte.py">Python program</a>]</p>
</li>
<li>
<p>After using this auto testing script, everything of output will be marked in log files of seafile, which located in <strong>~/.ccnet/log/seafile.log</strong></p>
</li>
<li>
<p>We need to use this simple awk code and vim operation to extract data.</p>
</li>
</ul>
<pre class="language-text"><code class="language-text"># CDC: content defined chucks
# HUT: Http upload traffic
# ALL: overall time of one commit &amp;amp; upload
awk '/CDC|HUT|ALL/ {print $4,$5}' ~/.ccnet/log/seafile.log &gt; results.stat
</code></pre>
<h4 id="install-seafile-on-odroid-xu" tabindex="-1">Install Seafile on odroid xu <a class="header-anchor" href="#install-seafile-on-odroid-xu" aria-hidden="true">#</a></h4>
<p>Due to failure of my cross-compile to seafile on android. I used develop board as a replacement experiment platform for ARM-seafile testing. I used a <a href="http://www.hardkernel.com/main/products/prdt_info.php?g_code=G137510300620">odroid xu</a> as hardware standard. Because all I need is an ARM platform, only an ARM-Ubuntu is enough for me. But develop prototype on a board is much fun than coding, I won’t address much this time. But I’ll start a blog telling some really cool stuff I made for a strange aim.</p>
<p>To install a ubuntu with GUI is my all preparation work. I found to way to do this.</p>
<ul>
<li></li>
</ul>
<p><a href="http://www.armhf.com/boards/odroid-xu/">armhf</a> is a website for arm-based ubuntu. It has a detailed instruction to follow at <a href="http://www.armhf.com/boards/odroid-xu/odroid-sd-install/">here</a>. They also provide ubuntu 12.04/ 14.04 and debian 7.5 to choose. But unfortunately odroid xu’s hdmi output doesn’t supported by ubuntu native firmware. So install ubuntu-desktop might can’t be boot up for video output.</p>
<ul>
<li></li>
</ul>
<p>Burn images is much easy to install a pre-complied ubuntu system. I found this on odroid xu’s forum, which contains xubuntu image [<a href="http://odroid.in/ubuntu_14.04lts/ubuntu-14.04lts-xubuntu-odroid-xu-20140714.img.xz">download</a>] for odroid xu. With this image, you just need to use dd command to write whole system mirror into sdcard.</p>
<pre class="language-text"><code class="language-text"># If .img end with xz, use this command to uncompress first
unxz ubuntu-14.04lts-xubuntu-odroid-xu-20140714.img.xz    
# Burn image into SD-card
sudo dd if=ubuntu-14.04lts-xubuntu-odroid-xu-20140714.img of=/dev/sdb bs=1M conv=fsync
sync
</code></pre>
<h2 id="2016-11" tabindex="-1">2016-11 <a class="header-anchor" href="#2016-11" aria-hidden="true">#</a></h2>
<h3 id="android-kernel" tabindex="-1">Android Kernel <a class="header-anchor" href="#android-kernel" aria-hidden="true">#</a></h3>
<h4 id="how-to-build-an-android-kernel%3F" tabindex="-1">How to build an Android Kernel? <a class="header-anchor" href="#how-to-build-an-android-kernel%3F" aria-hidden="true">#</a></h4>
<p>Generally, I won’t tell anything in this parts, just mark some related links, and point out some mistakes or error solutions.</p>
<ul>
<li></li>
</ul>
<p><a href="http://source.android.com/source/building-kernels.html#figuring-out-which-kernel-to-build">Google Official Guide</a></p>
<p>If you don’t have AOSP sources, you have to download prebuilt toolchains which recommended in this guide might not be correct. Use following links to choose your fitting tools.</p>
<p><a href="https://android.googlesource.com/?format=HTML">ASOP git root</a>, under sub class “/platform/prebuilts/gcc”</p>
<ul>
<li></li>
</ul>
<p><a href="https://softwarebakery.com/building-the-android-kernel-on-linux">Packing and Flashing a Boot.img</a> <strong>[highly recommend]</strong></p>
<h2 id="2016-12" tabindex="-1">2016-12 <a class="header-anchor" href="#2016-12" aria-hidden="true">#</a></h2>
<h3 id="android-kernel-1" tabindex="-1">Android Kernel <a class="header-anchor" href="#android-kernel-1" aria-hidden="true">#</a></h3>
<h4 id="how-to-compile-with-ftrace%3F" tabindex="-1">How to compile with ftrace? <a class="header-anchor" href="#how-to-compile-with-ftrace%3F" aria-hidden="true">#</a></h4>
<p>If we want to debug under android, ftrace is a great tool for working. But, ftrace is not available in android if we used default configure file. Android kernel configuration is in <strong>arch/arm64/kernel/configs</strong>. We need to add few lines under that.</p>
<pre class="language-text"><code class="language-text">CONFIG_STRICT_MEMORY_RWX=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_DYNAMIC_FTRACE=y
CONFIG_PERSISTENT_TRACER=y
CONFIG_IRQSOFF_TRACER=y
CONFIG_PREEMPT_TRACER=y
CONFIG_SCHED_TRACER=y
CONFIG_STACK_TRACER=y
</code></pre>
<h4 id="how-to-extract-android-images%3A-dump-an-image" tabindex="-1">How to extract android images: Dump an image <a class="header-anchor" href="#how-to-extract-android-images%3A-dump-an-image" aria-hidden="true">#</a></h4>
<p>If we want to hold a rooted status after flashing boot, we need to extract an image from android devices. We can first use following command to find which blocks belongs to. According to some references, <a href="http://forum.xda-developers.com/showthread.php?t=2450045">this article</a> provide three ways to dump an image, I picked one for easy using.</p>
<pre class="language-text"><code class="language-text">adb shell
ls -al /dev/block/platform/$SOME\_DEVICE../../by-name # {Partitions} -&gt; {Device Block}

# dump file
su
dd if=/dev/block/mmcblk0p37 of=/sdcard/boot.img
</code></pre>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/some-of-my-previews-experiment-works-2016/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Using charles proxy to monitor mobile SSL traffics]]></title>
            <link>https://pengzhan.dev/using-charles-proxy-to-monitor-mobile-ssl-traffics</link>
            <guid>https://pengzhan.dev/using-charles-proxy-to-monitor-mobile-ssl-traffics</guid>
            <pubDate>Thu, 27 Oct 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[In this blog, I will generally talk about how to use proper tools to monitor SSL traffics of a mobile devices. Currently, I only can dealing with those SSL traffics which use an ob...]]></description>
            <content:encoded><![CDATA[<p>In this blog, I will generally talk about how to use proper tools to monitor SSL traffics of a mobile devices. Currently, I only can dealing with those SSL traffics which use an obviously certification. Some applications may not using system root cert or they doesn’t provide us a method to modify their own certs. For these situation, I still didn’t find a good solutions for it. But I’ll keep updating this if I get one.</p>
<p>My current solution is using AP to forward all SSL traffic to a proxy, <a href="https://www.charlesproxy.com/">charles proxy</a> is my first choice (Prof asked). It’s a non-free software which still update new versions now. So mainly, I’ll talk about how to charles SSL proxy.</p>
<h3 id="preparations" tabindex="-1">Preparations <a class="header-anchor" href="#preparations" aria-hidden="true">#</a></h3>
<ul>
<li>
<p>Monitor device situation: Linux Machine with wireless adapter</p>
</li>
<li>
<p>Download the newest version(4.0.1) of charles</p>
</li>
<li>
<p>Target android devices with root privilege</p>
</li>
</ul>
<h3 id="install-charles-and-configuration" tabindex="-1">Install Charles and Configuration <a class="header-anchor" href="#install-charles-and-configuration" aria-hidden="true">#</a></h3>
<ul>
<li>You have to install charles first. After downloading the charles proxy, you have to unzip it and configure some basic settings.</li>
</ul>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># open charles first</span>
./bin/charles
</code></pre>
<ul>
<li>Save charles’ private key and public key</li>
</ul>
<p>In Help -&gt; SSL Proxying -&gt; Export Charles Root Certificate and Private Key, enter a password and save the public and private key in *.p12 format.
You also need to save charles Root Certificate, it also contains in the same menu. For convience, save it as *.pem format.</p>
<ul>
<li>Set Proxy and SSL Proxy</li>
</ul>
]]></content:encoded>
            <enclosure url="https://pengzhan.dev/posts-assets/using-charles-proxy-to-monitor-mobile-ssl-traffics/images/cover.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[STSD: Stop Talking Start Doing]]></title>
            <link>https://pengzhan.dev/hello-world</link>
            <guid>https://pengzhan.dev/hello-world</guid>
            <pubDate>Wed, 26 Oct 2016 00:00:00 GMT</pubDate>
            <enclosure url="https://pengzhan.dev/posts-assets/hello-world/images/cover.webp" length="0" type="image/webp"/>
        </item>
    </channel>
</rss>