Skip to content

Contact us

    Talk to our Experts


    +1 920 303 0470
    info@smart-is.com 1302 S Main ST , Oshkosh , WI 54902-6518

    How Many Users Are Really Concurrent? A Practical Approach to Software License Audits

    Enterprise software licenses are often based on named users. Sometimes, however, the license agreement is based on concurrent users.

    That sounds like a much simpler number than it actually is.

    If a vendor asks how many users were concurrently using a system over the last 30 days, what exactly should we count?

    Counting logins is clearly wrong. Counting users who performed transactions during the same hour is also wrong. Looking at currently connected sessions only tells us what is happening now. And relying on logout records assumes that every session ends cleanly—which is often not how real enterprise systems behave.

    We recently needed to solve this problem for a Blue Yonder WMS environment. The resulting approach is useful beyond Blue Yonder because the underlying problem is the same for any application where licensing depends on concurrent usage.

    You can download the script here.

    First, Define “Concurrent”

    Two users are concurrent if their active sessions overlap in time.

    Suppose:

    user a: 08:05 – 08:50
    user b: 08:20 – 09:10
    user c: 08:35 – 08:45

    At 08:10, concurrency is 1.

    At 08:25, it is 2.

    At 08:40, it is 3.

    So the answer for the 08:00 hour is not the number of users who used the application during that hour. All three did.

    The useful number is:

    Peak concurrency during the hour = 3

    That distinction matters. Counting distinct users who were active at some point during an hour measures usage, not concurrency.

    Now Define a Session

    The next problem is harder.

    A successful login gives us a very good session start:

    session_start = successful login timestamp

    A logout would appear to give us the end.

    Unfortunately, real systems do not always produce a corresponding logout. A workstation can disappear. A client can terminate unexpectedly. A connection can be interrupted. There are many ways for a session to start without leaving behind the logout event we would ideally like to see.

    We therefore need a deterministic definition of session end.

    For our implementation, a session ends at the earliest of:

    1. The next login or logout event for the same user.
    2. A configurable maximum session duration.
    3. The current time, if the session is still within that maximum duration.

    We use eight hours as the default maximum, but deliberately make it configurable.

    The important point is not that eight hours is universally correct. It is that an audit methodology needs an explicit, reproducible rule rather than allowing a missing logout to create an indefinitely active user.

    We define the resulting session as a half-open interval:

    [login_time, session_end)

    The user is active at the instant of login and inactive at the exact session-end timestamp.

    This avoids artificial overlap when one session ends at exactly the same instant another begins.

    Why Not Use Transaction Activity?

    Another tempting approach is to infer activity from the user’s last transaction.

    That creates a different problem.

    A user can be legitimately using the application without generating the type of business transaction we happen to be monitoring. They may be querying data, reviewing screens, investigating an order, or simply spending time between actions.

    A transaction timestamp tells us that a user did something at that instant.

    It does not reliably tell us when their authenticated session ended.

    For a concurrent-user audit, session evidence is therefore a better foundation than business transaction activity.

    We Don’t Need to Check Every Second

    Once sessions are defined, the brute-force solution would be to calculate active users every second—or perhaps every minute.

    That isn’t necessary.

    Concurrency can increase only when a session starts.

    When somebody logs out, concurrency can only decrease. When an inferred session reaches its maximum duration, concurrency can only decrease.

    Therefore, within each hour we only need to evaluate concurrency at:

    the beginning of the hour
    +
    every session start within the hour

    At each of those points we count the distinct users whose sessions contain that instant.

    Then:

    hourly_peak =

        max(concurrent_users at each evaluation point)

    This gives us exact peak concurrency for the hour without continuously sampling the system.

    Why the Hour Boundary Matters

    Consider a user who logs in at 07:45 and remains logged in until 08:30.

    If we looked only at login events occurring between 08:00 and 09:00, we would miss that user until somebody else logged in.

    That is why every hour begins with an evaluation point at exactly the hour boundary.

    At 08:00 we ask:

    which sessions started on or before 08:00
    and end after 08:00?

    Then we repeat the calculation at every new login occurring during that hour.

    The highest result is the peak for that hour.

    Multiple Instances Make It More Interesting

    Enterprise applications frequently have multiple instances.

    Suppose Instance A has two active users:

    a1: 08:05 – 08:55
    a2: 08:10 – 08:50

    Instance B then gets another login:

    b1: 08:20 – 08:40

    The enterprise concurrency at 08:20 is 3.

    There is a subtle trap here.

    If each instance independently calculates concurrency only at its own login timestamps and we combine the results afterward, Instance A never evaluates itself at 08:20. That timestamp originated on Instance B.

    We can therefore understate the true enterprise peak.

    The correct sequence is:

    derive sessions on every instance
    |
    normalize timestamps
    |
    combine sessions and candidate points
    |
    evaluate every session against every global candidate point
    |
    count distinct active users
    |
    take the maximum for each hour

    For our implementation, timestamps from the individual WMS instances are normalized to UTC before global concurrency is calculated.

    This also gives us two useful views:

    Local concurrency tells us the peak on each individual application instance in that instance’s local time.

    Global concurrency tells us the peak number of distinct users across the enterprise in UTC.

    If the same user is simultaneously connected to two instances, the global calculation still counts that user once. We are measuring concurrent users, not concurrent sessions.

    The Algorithm

    At a high level, the implementation looks like this:

    for each application instance in parallel:

        read login and logout audit events

        order events by user and timestamp

        for each successful login:

            session_start = login timestamp

            session_end = earliest of:
                next login/logout event
                maximum configured session duration
                current time

        determine which sessions are eligible for the audit

        generate local evaluation points:
            each hour boundary
            each login timestamp

        convert session times to utc

        generate global evaluation points:
            each utc hour boundary
            each login timestamp in utc

    combine data from all instances

    for local reporting:

        for each instance and evaluation point:

            count distinct users where:
                session_start <= evaluation_point
                and session_end > evaluation_point

        for each instance and hour:
            peak = maximum concurrency

    for global reporting:

        combine evaluation points from every instance

        for each global evaluation point:

            count distinct users across all instances where:
                session_start_utc <= evaluation_point
                and session_end_utc > evaluation_point

        for each utc hour:
            peak = maximum concurrency

    There is no sampling approximation in the concurrency calculation. Given the session boundaries we have defined, evaluating the hour boundary and session starts is sufficient to find the maximum.

    The only inference is the explicitly documented rule used to determine the end of a session when a definitive logout is unavailable.

    Making the Assumptions Visible

    For something that may be used in a license audit, I think this is particularly important.

    The goal should not be to hide assumptions inside code.

    The opposite is better.

    Expose them.

    Our implementation therefore makes parameters such as these explicit:

    maximum inferred session duration
    audit start date/time
    login signature
    logout signature
    whether system users are counted
    local vs. global reporting

    If eight hours is challenged as the maximum session duration, change it and rerun the analysis.

    If system/service accounts should not count toward the license, exclude them according to an agreed definition.

    The calculation remains deterministic and reproducible.

    That is much more defensible than producing a number whose underlying definition of “concurrent user” nobody can quite explain.

    The Blue Yonder Implementation

    We implemented this methodology for Blue Yonder WMS using MOCA and the WMS audit data.

    The implementation executes against multiple WMS instances in parallel, derives the session intervals on each instance, normalizes timestamps for enterprise-level analysis, combines the resulting data, and calculates both local and global hourly peaks.

    The complete script is attached here.

    It deliberately exposes the assumptions that affect the result rather than burying them in the implementation. That makes the script useful not only for producing the concurrent-user count, but also for explaining exactly how that number was derived.

    And for a software license audit, that second part may be just as important as the number itself.

    Saad Ahmad
    saad.ahmad@smart-is.com

    Recent Stories

    View All
    S
    Smart Solution Design Paradigm (SSDP): A WMS Modernization Case Study 
    Sep 24, 2026

    Transforming a Complex Legacy Landscape into a Scalable, Future-Ready Warehouse Platform  Abstract  In today’s rapidly evolving technology landscape, driven by continuous advancements in digital platforms, automation, and enterprise systems, organizations face increasing challenges in maintaining alignment between business operations and system capabilities. As warehouse management environments scale across multiple sites, the complexity of customizations, integrations,

    Read More
    USD 78 mil millones en oportunidad. Una pregunta crítica: ¿Pueden los almacenes de América Latina mantener el ritmo? 
    Sep 22, 2026

    Según el Banco Interamericano de Desarrollo (BID), el nearshoring podría generar aproximadamente USD 78 mil millones adicionales en exportaciones anuales en América Latina y el Caribe. Para fabricantes, retailers y distribuidores de la región, esto representa una de las oportunidades de crecimiento más importantes para la cadena de suministro en décadas.  Sin embargo, el crecimiento

    Read More
    Nearshoring y la brecha en la ejecución del almacén en Latinoamérica  
    Aug 06, 2026

    El nearshoring está transformando las cadenas de suministro globales y posicionando a América Latina como uno de sus principales beneficiarios. A medida que fabricantes y retailers acercan la producción y la distribución a los mercados finales, la inversión en la región continúa acelerándose.  Los datos de nearshoring y cadena de suministro mencionados a lo largo de este blog provienen de investigaciones

    Read More
    Blog Feature Image
    ForgeViu: Visual Experiences for an Intent-Centric Enterprise
    Jun 12, 2026

    Technology has a habit of making us believe that every new breakthrough renders everything that came before it obsolete. When television became mainstream, many predicted the end of radio. When e-commerce emerged, some predicted the end of physical retail. When smartphones arrived, there were predictions that desktop computing would become irrelevant. Reality is usually more

    Read More
    Beyond the Crawl: A Coding Agent for Proprietary Programming Languages
    May 17, 2026

    General-purpose coding models perform well on languages that are well-represented on the public internet. They fail predictably on proprietary or domain-specific languages: hallucinated identifiers, wrong call shapes, and confident reproduction of deprecated patterns. MOCA, the scripting language inside Blue Yonder’s Warehouse Management System, is one of those failure cases, the entire ecosystem (grammar, command catalog,

    Read More
    The Generalist Manifesto: Why “Broad Intelligence + Skill” Is the Future of AI
    May 08, 2026

    For the past couple of years, we’ve seen a massive push toward Specialized LLMs. The argument seems intuitive: if you need legal expertise, use a legal model. If you need medical expertise, use a medical model. Why rely on a “general” model when you can have one trained specifically for the task at hand? In

    Read More
    Safety by Ignorance vs Safety by Understanding: What the Claude–DoD Debate Is Really About
    Apr 03, 2026

    There is a growing debate in AI governance that is often framed as a dispute over “safeguards,” “alignment,” or “responsible AI.” A recent flashpoint involves models developed by Anthropic (notably Claude) and the U.S. Department of Defense, but the disagreement is deeper than any single vendor or contract. At its core, the debate is not

    Read More
    From Chat to Capability: Operationalizing Enterprise AI Intents
    Apr 02, 2026

    In earlier posts, I argued two related ideas: This post builds directly on those ideas and takes the next logical step. If intent is the real abstraction — and if enterprises need repeatable, governed intelligence — then the obvious question is: Where does intent actually live, and how does it execute? The Missing Layer: Intent

    Read More
    Enterprise Cognitive Intelligence, Without the Complexity
    Feb 17, 2026

      In our previous post, we showed how our MCP framework removes the friction from exposing system capabilities to AI. MCP solves access. Cognition requires meaning. Because enterprises don’t think in systems.They think in business concepts. The Limits of System-Level Intelligence Most MCP servers today expose a single system — ERP, WMS, OMS, planning engines —

    Read More
    From APIs to Intelligence: How Our MCP Framework Makes Any System AI-Ready
    Feb 17, 2026

    Everyone wants to “add AI” to their systems — but few succeed. Not because AI models aren’t powerful — they are. But because exposing enterprise systems to AI is still too hard. So for most teams, the friction is simply too high. A Useful Analogy: AI as a Researcher Think of AI as a researcher. At

    Read More