Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
159968 stories
·
33 followers

Finding the total number of processors on a machine with .NET

1 Share

This is a post about a simple concept. In .NET, I want to know how many processors exist on a host/VM. However, as far as I can tell, there's no APIs for that in modern .NET. If you need that information, this post shows the only approach I could come up with, which involves a P/Invoke on some platforms, and parsing files on Linux!

If there's a better way, please tell me, I kind of hate what I've had to do here 😅

Why not Environment.ProcessorCount?

Hopefully someone is thinking "Why wouldn't you just use Environment.ProcessorCount". After all, it's been available since .NET Framework 2.0! Unfortunately, what this value actually means depends on which version of .NET you're using…

  • .NET Framework—Returns the number of logical processors on the host machine (i.e. exactly what I want 🎉)
  • .NET Core < 6—Returns the number of logical processors on the host machine, but is container aware (to an extent, though it is buggy)
  • .NET Core 6+—Returns the number of logical processors on the host machine unless you're running with process affinity, or you're running in a container. Essentially it returns the number of processors available to the process.

Ironically, .NET Framework actually does exactly what I need and modern .NET doesn't 😅 That's generally understandable, as normally it's most useful to know how many processes a process has available, rather than how many the host has, but in this case, that's not what I want.

So what options do we have?

Could Microsoft.Extensions.Diagnostics.ResourceMonitoring be the answer?

Betteridge's law of headlines comes the for, the answer is "No" 😅

If you weren't aware, the Microsoft.Extensions.Diagnostics.ResourceMonitoring NuGet package provides a collection of APIs for monitoring the resource utilization (CPU, memory, network) of your .NET applications.

It provides two sets of APIs

You can use these metrics to emit a host of resource metrics:

  • container.cpu.limit.utilization
  • container.cpu.request.utilization
  • container.cpu.time
  • container.memory.limit.utilization
  • container.memory.usage
  • process.cpu.utilization
  • dotnet.process.memory.virtual.utilization
  • system.network.connections

However, you'll note that none of those metrics is the number of host processors. So we're out of luck.

Calling native APIs to get the details

I'll cut to the chase: the only way I found to retrieve the values I was after was to call native APIs:

  • On Windows, we need to P/Invoke GetActiveProcessorCount()
  • On macOS, we need to P/Invoke sysctlbyname("hw.logicalcpu")
  • On Linux, we need to read and parse /sys/devices/system/cpu/online

This is much messier than I had hoped, so if someone has a better approach, I'm all ears! Nevertheless, the following sections describe how to read each of these values on the three platforms.

Getting the total CPU count on Windows

I'll start with Windows, as it's one of the simplest. We simply make a call into the kernel, and invoke GetActiveProcessorCount passing in the "All processor groups" flag, so that we get the total number of processors on the system:

internal static class WindowsProcessorCount
{
    private const ushort AllProcessorGroups = 0xFFFF;

    internal static int? GetTotalProcessorCount(ILogger log)
    {
        var result = GetActiveProcessorCount(AllProcessorGroups);
        if (result > 0)
        {
            return result;
        }

        var error = Marshal.GetLastPInvokeError();
        log.LogWarning(
            "GetActiveProcessorCount failed when getting total machine processor count. ErrorCode={ErrorCode}",
            property: error);
        return null;
    }  

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern int GetActiveProcessorCount(ushort groupNumber);
}

This is very simple - the GetTotalProcessorCount method simply P/Invokes and returns the number of processors. If the returned value is 0 then we had an error, so we log it and return null.

Note that I've used [DllImport] in all these examples, as I needed to support .NET 6, but if you can, you should probably use [LibraryImport] instead.

That's Windows covered, on to the next OS!

Getting the total CPU count on macOS

The code on macOS is similarly a single P/Invoke, however it uses the generic sysctlbyname library function which requires a bit more faffing with arguments than Windows. Ultimately, it has essentially the same pattern as the Windows code.

internal static class MacOsProcessorCount
{
    private const string LogicalCpuName = "hw.logicalcpu";

    internal static int? GetTotalProcessorCount(ILogger log)
    {
        var size = new IntPtr(sizeof(int));
        var result = SysCtlByName(LogicalCpuName, out var value, ref size, IntPtr.Zero, IntPtr.Zero);

        if (result == 0 && value > 0)
        {
            return value;
        }

        var error = Marshal.GetLastPInvokeError();
        log.LogWarning(
            "sysctlbyname failed when getting total machine processor count. ErrorCode={ErrorCode}",
            property: error);
        return null;
    }

    [DllImport("libSystem.dylib", EntryPoint = "sysctlbyname", CharSet = CharSet.Ansi, SetLastError = true)]
    private static extern int SysCtlByName(
        string name,
        out int oldp,
        ref IntPtr oldlenp,
        IntPtr newp,
        IntPtr newlen);
}

So we essentially have the same pattern here: make the P/Invoke, check the result and return the value. Nothing too bad (though you obviously have to make sure to get the P/Invoke API correct, thankfully something that LLMs are very good at these days).

The final platform we have is Linux, which is where things get a bit different.

Getting the total CPU count on Linux

On Linux, rather than making a P/Invoke into a library, we instead read from the /sys/devices/system/cpu/online file, parse the list of CPUs and return the result.

Note that you could make a P/Invoke into the C library and call sysconf(_SC_NPROCESSORS_ONLN), but there were edge cases with calling sysconf I wanted to avoid, such as the fact the constant is difference on glibc vs musl etc. By reading the file directly, we avoid those issues.

internal static class LinuxProcessorCount
{
    private const string OnlineCpusPath = "/sys/devices/system/cpu/online";

    internal static int? GetTotalProcessorCount(ILogger log)
    {
        try
        {
            var contents = File.ReadAllText(OnlineCpusPath);
            var result = TryParseOnlineCpuRanges(contents.AsSpan());

            if (result is null)
            {
                Log.LogWarning(ex, "Parsing cpu-list failed: contents was not a valid cpu-list '{FileContents}'", contents);
            }
        }
        catch (Exception ex)
        {
            Log.LogWarning(ex, $"Error reading '${OnlineCpusPath}' to determine total machine processor count");
            return null;
        }
    }

    // Parses the Linux cpu-list-format (see https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt)
    // This is a comma-separated list of either a single CPU index ("0") or an inclusive range ("0-7"), e.g. "0-3,4,8-11".
    internal static int? TryParseOnlineCpuRanges(ReadOnlySpan<char> contents)
    {
        var trimmed = contents.Trim();
        if (trimmed.IsEmpty)
        {
            return null;
        }

        var count = 0;
        var remaining = trimmed;
        while (!remaining.IsEmpty)
        {
            // Find the next token
            var commaIndex = remaining.IndexOf(',');
            var token = commaIndex < 0 ? remaining : remaining[..commaIndex];

            if (!TryParseToken(token, out var tokenCount))
            {
                // Should never happen, means the file contained invalid data
                return null;
            }

            // Increase the CPU count
            count += tokenCount;

            if (commaIndex < 0)
            {
                // All done
                break;
            }

            // Cut off the values we just read
            remaining = remaining[(commaIndex + 1)..];
            if (remaining.IsEmpty)
            {
                // trailing comma with no following token
                return null;
            }
        }

        // If we didn't read any values, something weird happened
        return count > 0 ? count : null;

        // Parse either a single value like "4", or a range, like "3-7"
        static bool TryParseToken(ReadOnlySpan<char> token, out int tokenCount)
        {
            tokenCount = 0;

            var dashIndex = token.IndexOf('-');
            if (dashIndex < 0)
            {
                // A single value
                if (!int.TryParse(token, out var single) || single < 0)
                {
                    return false;
                }

                tokenCount = 1;
                return true;
            }

            // Parse each value in the range
            var startSpan = token[..dashIndex];
            var endSpan = token[(dashIndex + 1)..];

            if (!int.TryParse(startSpan, out var start) || start < 0 ||
                !int.TryParse(endSpan, out var end) || end < start)
            {
                return false;
            }

            // Count the number covered by the range, e.g 0-3 = 4 CPUs
            tokenCount = end - start + 1;
            return true;
        }
    }
}

As I said earlier, this is a little annoyingly convoluted, but it's not complicated, it's just reading a file and parsing the contents 🙂

Putting it all together

So we now have a method for reading the total CPUs on each platform we can put it all together into one convenience method, that calls the correct API based on the platform:

internal static class TotalProcessorCount
{
    internal static int? GetTotalProcessorCount(ILogger log)
    {
        if (OperatingSystem.IsWindows())
        {
            return WindowsProcessorCount.GetTotalProcessorCount(log);
        }

        if (OperatingSystem.IsLinux())
        {
            return LinuxProcessorCount.GetTotalProcessorCount(log);
        }

        if (OperatingSystem.IsMacOS())
        {
            return MacOsProcessorCount.GetTotalProcessorCount(log);
        }

        return null;
    }
}

For this post I created the helper as a simple static type, but you would likely want to cache the value returned from GetTotalProcessorCount() seeing as it won't change for the lifetime of the process (unless we've got something wrong!). I'll leave that as an exercise for the reader, but for completeness, this is the full type, with the helper types nested inside to encapsulate them away

internal static class TotalProcessorCount
{
    internal static int? GetTotalProcessorCount(ILogger log)
    {
        if (OperatingSystem.IsWindows())
        {
            return WindowsProcessorCount.GetTotalProcessorCount(log);
        }

        if (OperatingSystem.IsLinux())
        {
            return LinuxProcessorCount.GetTotalProcessorCount(log);
        }

        if (OperatingSystem.IsMacOS())
        {
            return MacOsProcessorCount.GetTotalProcessorCount(log);
        }

        return null;
    }

    private static class WindowsProcessorCount
    {
        private const ushort AllProcessorGroups = 0xFFFF;

        internal static int? GetTotalProcessorCount(ILogger log)
        {
            var result = GetActiveProcessorCount(AllProcessorGroups);
            if (result > 0)
            {
                return result;
            }

            var error = Marshal.GetLastPInvokeError();
            log.LogWarning(
                "GetActiveProcessorCount failed when getting total machine processor count. ErrorCode={ErrorCode}",
                property: error);
            return null;
        }  

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern int GetActiveProcessorCount(ushort groupNumber);
    }

    private static class MacOsProcessorCount
    {
        private const string LogicalCpuName = "hw.logicalcpu";

        internal static int? GetTotalProcessorCount(ILogger log)
        {
            var size = new IntPtr(sizeof(int));
            var result = SysCtlByName(LogicalCpuName, out var value, ref size, IntPtr.Zero, IntPtr.Zero);

            if (result == 0 && value > 0)
            {
                return value;
            }

            var error = Marshal.GetLastPInvokeError();
            log.LogWarning(
                "sysctlbyname failed when getting total machine processor count. ErrorCode={ErrorCode}",
                property: error);
            return null;
        }

        [DllImport("libSystem.dylib", EntryPoint = "sysctlbyname", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern int SysCtlByName(
            string name,
            out int oldp,
            ref IntPtr oldlenp,
            IntPtr newp,
            IntPtr newlen);
    }

    internal static class LinuxProcessorCount
    {
        private const string OnlineCpusPath = "/sys/devices/system/cpu/online";

        internal static int? GetTotalProcessorCount(ILogger log)
        {
            try
            {
                var contents = File.ReadAllText(OnlineCpusPath);
                var result = TryParseOnlineCpuRanges(contents.AsSpan());

                if (result is null)
                {
                    Log.LogWarning(ex, "Parsing cpu-list failed: contents was not a valid cpu-list '{FileContents}'", contents);
                }
            }
            catch (Exception ex)
            {
                Log.LogWarning(ex, $"Error reading '${OnlineCpusPath}' to determine total machine processor count");
                return null;
            }
        }

        // Parses the Linux cpu-list-format (see https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt)
        // This is a comma-separated list of either a single CPU index ("0") or an inclusive range ("0-7"), e.g. "0-3,4,8-11".
        private static int? TryParseOnlineCpuRanges(ReadOnlySpan<char> contents)
        {
            var trimmed = contents.Trim();
            if (trimmed.IsEmpty)
            {
                return null;
            }

            var count = 0;
            var remaining = trimmed;
            while (!remaining.IsEmpty)
            {
                // Find the next token
                var commaIndex = remaining.IndexOf(',');
                var token = commaIndex < 0 ? remaining : remaining[..commaIndex];

                if (!TryParseToken(token, out var tokenCount))
                {
                    // Should never happen, means the file contained invalid data
                    return null;
                }

                // Increase the CPU count
                count += tokenCount;

                if (commaIndex < 0)
                {
                    // All done
                    break;
                }

                // Cut off the values we just read
                remaining = remaining[(commaIndex + 1)..];
                if (remaining.IsEmpty)
                {
                    // trailing comma with no following token
                    return null;
                }
            }

            // If we didn't read any values, something weird happened
            return count > 0 ? count : null;

            // Parse either a single value like "4", or a range, like "3-7"
            static bool TryParseToken(ReadOnlySpan<char> token, out int tokenCount)
            {
                tokenCount = 0;

                var dashIndex = token.IndexOf('-');
                if (dashIndex < 0)
                {
                    // A single value
                    if (!int.TryParse(token, out var single) || single < 0)
                    {
                        return false;
                    }

                    tokenCount = 1;
                    return true;
                }

                // Parse each value in the range
                var startSpan = token[..dashIndex];
                var endSpan = token[(dashIndex + 1)..];

                if (!int.TryParse(startSpan, out var start) || start < 0 ||
                    !int.TryParse(endSpan, out var end) || end < start)
                {
                    return false;
                }

                // Count the number covered by the range, e.g 0-3 = 4 CPUs
                tokenCount = end - start + 1;
                return true;
            }
        }
    }
}

Should I use this code?

That's entirely up to you 😅 I haven't yet shipped this code into production, but I'm seriously considering it. I think it's pretty sound as best as I (and the 🤖) can tell, but obviously use your own judgement. As I said before, if you know of a better way to get these values, I'd be very interested to hear about it in the comments.

The one thing I would suggest changing if you're using modern .NET applications with dependency injection etc, is to nest all this code inside a little singleton wrapper that caches the value for the lifetime of the process and provides an ILogger instance to use etc. But otherwise, try it out, make sure it works for you!

Summary

In this post I talked about how to find the total number of CPUs available on a host, as opposed to the number of CPUs available to a process. Environment.ProcessorCount returns the former in .NET Framework, but in .NET Core, it returns the latter (and you can actually trust the values from about .NET 6+). However, in .NET 6+, if you actually want the total number of CPUs on the host, then there are no managed APIs I could find in the BCL to achieve that.

As a consequence, in this post, I show how to find the total processor count on Windows, macOS, and Linux. For Window and macOS, we can use a simple P/Invoke to read the value. This is theoretically available on Linux, but it's a bit harder than you might expect, so instead of using P/Invoke, I show how to read and parse the /sys/devices/system/cpu/online file instead. Finally, I put all three approaches into a helper that switches based on the current platform.

Read the whole story
alvinashcraft
11 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Cursor Releases Origin as an Agent-Native Alternative to GitHub

1 Share

AI coding agent Cursor has launched Origin, a git based code hosting platform embedded inside its AI-powered editor, positioning it as an alternative to GitHub for teams that already work in Cursor. Origin is rolling out in early beta on Pro, Teams and Enterprise plans, and lives inside a new Codebase tab within the Cursor application.

By Matt Saunders
Read the whole story
alvinashcraft
25 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Using Historical Data to Confirm Performance Improvements

1 Share

Using historical data to confirm performance improvements turns a fast test into evidence the business can defend.

Using historical data to confirm performance improvements starts with one fair question. Did the same work become cheaper under comparable conditions? In an illustrative case, a DBA deploys an index at 9:00 AM and watches average reads fall. The release channel fills with check marks.

Before deployment, the query averaged 42,000 logical reads across 18,400 executions. Afterward, it averaged 6,100 reads across 11,200 executions. Total reads fell 91 percent, but executions fell 39 percent too.

That missing context changes the decision. The team now needs matched windows, stable plans, and comparable parameters. Enough executions must pass before anyone calls the index a success.

One Fast Execution Proves Almost Nothing

A clean test confirms a change for one parameter set. It cannot represent every customer, data distribution, or concurrency pattern. Cached pages and quiet servers can flatter results while hiding blocking, memory pressure, or storage latency.

Start with per-execution duration, CPU, logical reads, writes, and waits. Then add execution count. If executions double after latency halves, aggregate duration stays level. Total CPU and reads still require separate calculations.

Both views matter because users experience individual calls, while servers absorb the complete workload. Application growth may raise daily CPU despite better per-call latency. Modest per-call gains may also save substantial capacity on frequent statements. Report both perspectives instead of selecting the flattering number.

Using Historical Data to Confirm Performance Improvements historical-performance-proof-scaled

Use a Before-and-After Scorecard

Measure What to Compare
Duration Typical behavior and slow outliers
CPU and reads Per execution and total workload
Executions Count, application, and parameter mix
Plan Plan identity and estimate quality
Writes DML latency, log volume, and maintenance

Build Comparable Workload Windows

Compare Monday morning with another normal Monday morning, not Sunday night. Match business cycles, batch schedules, release activity, and expected traffic. Keep important database, hardware, and configuration conditions consistent.

The plan may change when a large customer replaces a small one. Match applications, databases, users, query signatures, and parameter patterns. Note any statistics update or plan change inside either window.

Use several windows when the workload varies naturally. One favorable hour may be noise, while repeated improvements establish a pattern. Include enough executions to limit isolated outliers, and preserve the exact deployment time.

Normalize totals when windows cover different durations, but never hide the raw values. Rates per minute help compare uneven windows, while counts preserve capacity impact. Separate scheduled jobs from interactive traffic when their patterns differ. Otherwise, one overnight process can make a healthy daytime change look unsuccessful.

Read Product History With Context

SQL DM from IDERA charts query history for average duration, CPU, reads, writes, waits, blocking, deadlocks, and CPU per second. Event occurrences add execution-level statistics and SQL text. Now the graph answers the useful question: did the query stay faster during real traffic?

That history still reflects collection settings. Filters, thresholds, disabled monitoring, and retention choices can create gaps. Older query records may be aggregated into daily summaries, which suppresses some statement, client, and user detail. Repository grooming can also remove data beyond the configured retention period.

Use Query Store as a Second Witness

Query Store persists query text, plans, and runtime statistics. SQL Server 2017 and later can capture query-level waits. This historical evidence can connect an improvement with an index, plan, or workload change.

Query Store is not a recording of every execution. Runtime statistics are aggregated into configurable time intervals. Its averages, minimums, maximums, and standard deviations describe each plan within those intervals. Capture policies, cleanup settings, and storage limits determine what remains available.

Compare plan identifiers as well as query identifiers, because lower duration may come from an unrelated new plan. A forced plan or statistics refresh may alter the result. The claim gets stronger when the plan, change, and result share one clear timeline.

Measure the Cost of the Improvement

An index can reduce reads for selected queries while increasing work for data changes. Check insert, update, and delete activity on the affected table. Review index size, maintenance time, logging, lock behavior, and storage consumption. Confirm that neighboring queries did not regress.

Native index usage counters can reveal seeks, scans, lookups, and update maintenance. However, those counters reset after events such as a server restart. Record the observation start time, because a short window may miss monthly reports depending on the index.

Define success and guardrails before deployment, such as lower reads without raising write latency beyond an agreed threshold. Capture the same metrics after deployment for an equivalent business window. Keep a rollback script available until the evidence remains stable.

The Fair Counterargument

Controlled benchmarks can demonstrate causality better than messy production history. A test regression costs nothing, while a production regression costs customers. That argument holds when test data and execution conditions represent production. Laboratory testing makes repeated measurements safer, especially when schema changes carry real risk.

However, controlled tests remove the concurrency, parameter diversity, and operational surprises that often determine production performance. Historical monitoring supplies that missing context. The strongest conclusion combines controlled testing with comparable production windows. Neither source should carry the decision alone.

A Result Worth Keeping

Baselines provide a comparison. SQL DM from IDERA supports a moving seven-day dynamic baseline and fixed custom periods. Choose normal periods and exclude quiet hours that distort expected behavior. A baseline is a reference, not an automatic verdict.

A trustworthy report shows the gain and every reason it might be misleading. Name the change, workload window, execution count, plans, and resource effect. Document competing deployments, missing data, and the period of stable behavior.

The DBA from 9:00 AM should wait through the next comparable peak. If reads stay lower and the guardrails hold, the change has earned its place. Then write the result down, because next quarter nobody will remember the details.

A fast test opens the case. A faster workload earns the decision.

Reference: Pinal Dave (https://blog.sqlauthority.com/), X

First appeared on Using Historical Data to Confirm Performance Improvements

Read the whole story
alvinashcraft
42 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

T-SQL Tuesday #201 Round-Up: Temp Tables, Friend or Foe?

1 Share

The T-SQL Tuesday #201 round-up: nine bloggers on whether temp tables are a friend or a foe, from full-throated defenses to a lab built to prove me wrong.

The post T-SQL Tuesday #201 Round-Up: Temp Tables, Friend or Foe? appeared first on SQLServerCentral.

Read the whole story
alvinashcraft
48 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Understanding the Inner Workings of AI: The Crucial Role of Agentic Harnesses

1 Share

Artificial Intelligence is rapidly transforming industries, and the way AI systems are crafted affects their power and versatility. When we think about AI, it’s common to focus heavily on AI models—those formidable neural networks like ChatGPT or Claude. These models are indeed the engines behind AI capabilities, but they’re only part of the equation. What truly sets one AI system apart from another is what surrounds these models: the agentic harness.

The term “agentic harness” might be new to some, but it refers to the critical components that enable AI models to interact with our world. These include tools, memory, and agentic loops. While an AI model, in its pure form, can act like a brain locked in a jar—packed with potential but unable to extend its influence outside that confines—the agentic harness unleashes this potential, empowering the AI to engage with the real world.

Based on content from IBM Technology

Unpacking the Agentic Harness

The agentic harness essentially comprises three key components: tools, memory, and loops. Let’s delve into each of these to appreciate how they enhance AI model performance.

Tools

Tools act as extensions that enable AI models to perform tasks similar to human capabilities. For instance, they allow the model to read from and write to files, run code within a sandbox environment, retrieve information from the internet, and even control elements on a computer screen. These operations mimic how a human developer might navigate and command digital environments.

Moreover, tools within a harness allow for seamless integration with existing machine software and external services through standards like the Model Context Protocol (MCP). This compatibility enriches the model’s ability to process data and execute tasks efficiently.

Memory

AI models typically operate within a fixed context window, akin to our short-term memory, which is limited in scope. Yet, the agentic harness can enhance memory persistence by saving crucial session data, such as instructions or codebase conventions, beyond the typical session limits.

By compacting the context window and retaining only relevant data, the harness enables the AI to focus on what’s essential, optimizing its processing power and ensuring it can access pertinent information when needed, without being bogged down by redundant data.

Agentic Loops

Perhaps the most dynamic component of the harness is the agentic loop, where the model collaborates with the processes outlined by the harness to achieve specific goals. In this loop, the model formulates plans, and the harness implements these plans, evaluates the outcomes, and adjusts for further actions—creating a continuous cycle of improvement and optimization.

Modern harnesses also incorporate verification stages within these loops, employing additional models to test, review, and refine outputs, ensuring accuracy and efficiency over longer tasks.

The Significance of the Distinction

Recognizing the roles of both AI models and agentic harnesses is vital for understanding and enhancing AI capabilities. The significant advancements we’ve noticed, particularly in AI’s practical applications, hinge on innovations within the harness, notably in tool enhancement, memory handling, and sophisticated loop designs.

For any given task, the question of AI’s ability must consider not just the model in use but the nature of the harness that accompanies it. This dual focus clarifies why some AI implementations excel where others falter—even when leveraging similar core models.

As AI technology progresses, the boundary between models and harnesses evolves too. Capabilities once exclusively managed by the harness are increasingly being incorporated into models themselves, just as various harness functionalities become more sophisticated.

Understanding how these two elements interact and complement each other is critical for developing robust AI systems that resonate well with real-world applications. As we continue to push the envelope of AI’s potential, appreciating the synthesis of models and their harnesses will become ever more essential.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Daily Reading List – August 25, 2026 (#853)

1 Share

I’m in Sunnyvale at Google Cloud HQ today and tomorrow. On the flight up, I finally got my generative UI service working properly in Gemini Enterprise. There are few things more satisfying that having an idea and then seeing it come to life with software.

[article] Google brings Antigravity under Gemini Enterprise to provide granular spend controls. This is the next frontier. How do we make AI work better within teams, not just for individuals?

[article] Can “Predictable Delivery” be measured? Interesting question. Two teams could have wildly different output per sprint, but have the same throughput over time.

[blog] Now introducing Gemini Enterprise for Financial Services. Smart offering, and you’ll see more things like this. Industry-specific AI is still in the early stages. See our legal offering as well.

[blog] The Mundanity of Excellence. Love it. Mundane tasks done over and over again don’t have to “boring” if you attach meaning and purpose.

[blog] Cloud CISO Perspectives: Sticking to security fundamentals in the AI era. CISO’s are more valuable than ever, if focused in the right places. This gives some perspective, along with many links for deeper learning.

[blog] Human judgment doesn’t leave the software factory. It relocates. Long post, but very worthwhile read from Addy. If you keep hearing this “software factory” phrase but aren’t sure what it means or when you’d use it, read on.

[blog] Architects, testers, and coders: Building multi-agent development teams. Andrew uses an agent team to port a popular Python library to a statically-typed Dart package. He experienced friction, iterated, and ended up learning some things. Good experiment!

[article] The AI-Native SDLC playbook. Fascinating writeup from Anthropic where they explore the SLDC stages with AI-lens. Same “work” but done very differently.

[blog] Deploy your App Engine apps to Cloud Run in a single command. When people find a stack they like, it’s hard to get them to switch! 15+ years after launching it, we still have a hearty customer base. This post shows the one-line command to switch over to the more modern Cloud Run.

[article] Not every problem needs an AI agent. Blasphemy, I know. Valid points here, but I also wonder if the box the author puts AI into will quickly dissolve.

Want to get this update sent to you every day? Subscribe to my RSS feed or subscribe via email below:



Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories