CSWorks: web-based industrial automation

Of CSWorks and software development

About resource hierarchy in web.config files

clock November 12, 2010 17:55 by author Sergey Sorokin


CSWorks stores resource hierarchy in web.config files, so:

  • LiveData Web Service can find LiveData Service instance that collects data from some specific data source;
  • Alarm Web Service can find Alarm Service that handles some specific alarm group;
  • History Reader Web Service can find database that holds historical data for a specific data point.


Let’s take History Reader Web Service for example. The following piece of web.config tells web service that data history for data point “7cc32718-0da0-42b3-ae55-0acbb3593671" (also known as "Tank1") is stored in the database “partition1”:

<historyTopology>
  <historyPartitions>

    <historyPartition name="partition1" primaryDbTarget="…" secondaryDbTarget="…">

      <areas>

        <area id="…" name="By Equipment/Location">

          <areas>

            <area id="…" name="Tanks">

              <areas>

                <area id="…" name="West Side">

                  <dataPoints>

                    <dataPoint id="{7cc32718-0da0-42b3-ae55-0acbb3593671}" description="Tank1"/>


One potential problem is that some solutions can record thousands of historical data points, and all of them must be referenced in a single web.config file, and by default, ASP.NET limits the size of web.config file by 256kb. Consider the following setup.


Run a script that generates 10,000 historical datapoint references (you can download it here) and insert them into <historyPartition name="partition1"…> node of the web.config of History Reader Web Service. Web.config now has size around 1mb (see screenshot below). Run any client application that uses this web service (CSWorks TrendDemo for example) and get a web service error that doesn’t leave any trace: no exceptions in ASP.Net worker process, no error in the Event Log.


Now tell ASP.NET that it should accept bigger web.config files. Create DWORD registry value HKLM\Software\Microsoft\InetStp\Configuration\MaxWebConfigFileSizeInKB and set its value to, say 1024kb (see screenshot below).


Now restart the client application and start accessing those 10,000 historical data points. The screenshot below shows:

  • registry editor with MaxWebConfigFileSizeInKB value;
  • web.config file size;
  • a fragment of web.config that references historical data points that belong to area “#0199”;
  • a fragment of CSWorks Trend Control that allows to choose any historical data point from area “#0199” and add it to the chart.


Click to enlarge


There are few other potential problems with large amount of data in web.config files. First, Silverlight Tree Control has problems with displaying more than a few thousand nodes simultaneously. Second, CSWorks History Reader Web Service handles data point hierarchy more efficiently if every area doesn’t contain more than a few hundred data points.


The recommendation is obvious: when planning historical data point areas hierarchy, do not put more than a few hundred data points in a single area, avoid “plain” hierarchies with thousands of data points in the same area. The script used in this demo (you can download it here) generates 200 areas each containing 50 historical data points.


This discussion also applies to LiveData and Alarm web services, although the problem of large web.config files is not that relevant for live data and alarming: only extremely large CSWorks solutions use more than thousand data sources and more than thousand alarm groups.



Trending client performance - Lab 03

clock May 19, 2010 19:18 by author Sergey Sorokin

If you are curious about the number of Trend controls you can run against your CSWorks server infrastructure, you may find this post interesting. We will make minor changes to historical data server configuration, run a few Trend control clients against it and analyze what we see.

Environment

CSWorks 1.2.3800.0
Server: Intel Core 2 Duo @ 2.40GHz, 2 GB RAM, Windows Server 2008
Client: Intel Core 2 Duo T5300 @ 1.73GHz (notebook), 2 GB RAM, Windows XP SP3
Network: Wireless 54 Mbps

Server Configuration

1. As usual, we have to configure History Recorder so it uses some scalable database. Install SQL Server 2008 Express on your server machine.

2. Create database "CSWorks"

3. Create HistoricalData table - see "createCommand" parameter of <dbtarget ...="" name="Standard SQLServer DbTarget"> in CSWorks.Server.HistoryRecorderService.exe.config.

4. Configure SQL Server data source and make it active in CSWorks.Server.HistoryRecorderService.exe.config:

<dbTargetConfig>
  <dbTargets activeDbTarget="Standard SQLServer DbTarget">
    <dbTarget name="Standard SQLite DbTarget" ...
      ...
    />
    <dbTarget name="Standard SQLServer DbTarget"
      providerInvariantName="System.Data.SqlClient"
      connectionString="Data Source=localhost\sqlexpress; Initial Catalog=CSWorks;user id=sa;password=...;"
      ...
    />
  </dbTargets>
</dbTargetConfig>


5. Configure HistoryReaderWebService to read historical data from this database. In the web.config, assign SQLServer target to the primary partition and specify correct connection string:

  <historyTopology>
    <historyPartitions>
      <historyPartition name="partition1" primaryDbTarget="partition1 Primary DbTarget (SQLServer)" secondaryDbTarget="">
        ..
      </historyPartition>
    </historyPartitions>
  </historyTopology>

  <dbTargetConfig>
    <dbTargets>
      ...
      <dbTarget name="partition1 Primary DbTarget (SQLServer)"
                providerInvariantName="System.Data.SqlClient"
                connectionString="Data Source=localhost\sqlexpress; Initial Catalog=CSWorks;user id=sa;password=...;"
                ...
                />
    </dbTargets>
  </dbTargetConfig>

6. Restart HistoryRecorder service and verify that it writes observation to the newly configured database.

Running clients

Before running the clients, make sure you have prepared *.clientConfig in CSWorks.Client.TrendDemo.xap to run from a remote machine. Please see this post for details.

Now run a few Trend clients on your client machine using this command:

start iexplore "http://myserver/CSWorksDemo/TrendDemo.html"

I ran 25 instances, increasing the load by 5-instance chunks.

Results

All 25 clients run without problems, trending data (both live and historical) arrives without delays, server seems to be perfoming fine. Here is a screenshot made on the server machine. Clients consume about 200K of live and historical data every second, server machine uses about 35% of its CPU capacity. SQL Server and ASP.NET worker process are working hard (14% and 18%, respectively) to deliver historical data to the clients.

The spikes in data transfer rates mark moments when Trend control were re-querying bigger amounts of historical data - View->Tracking setting were set to On for all Trend control instances, and all instances were refreshing the whole picture synchronously (well, in chunks of 5 instances, of course).

The spikes in CPU consumption mark moments when I ran chunks of 5 IE instances.

Summary

Not bad for a commodity server box that runs every piece of the deployment: all CSWorks services (LiveData, Alarm, HistoryRecorder), web services and database engine. Potential bottlenecks are:
- the database - not surprising;
- web service layer - but we can scale it out using web farm.



History Recorder performance - Lab 02

clock April 20, 2010 15:09 by author Sergey Sorokin

In the latest release of CSWorks, we have improved Historical Data Server performance. Let's see what History Recorder is capable of now.

Environment

CSWorks 1.2.3730.0
Server: Intel Core 2 Quad Q6600 @ 2.40GHz, 4 GB RAM, Windows 7

Set-up

1. Install SQL Server 2008 Express on your server machine.

2. Create database "CSWorks"

3. Create HistoricalData table - see "createCommand" parameter of in CSWorks.Server.HistoryRecorderService.exe.config.

4. Configure SQL Server data source and make it active in CSWorks.Server.HistoryRecorderService.exe.config:

<dbTargetConfig>
  <dbTargets activeDbTarget="Standard SQLServer DbTarget">
    <dbTarget name="Standard SQLite DbTarget" ...
      ...
    />
    <dbTarget name="Standard SQLServer DbTarget"
      providerInvariantName="System.Data.SqlClient"
      connectionString="Data Source=localhost\sqlexpress; Initial Catalog=CSWorks;user id=sa;password=...;"
      createCommand="..."
      writeCommand="..."
      maintenanceCommand="delete top (300000) from HistoricalData ..."
      maxObservationAge="600"
      writeInterval="1"
      maintenanceInterval="30"
      maxQueryLength="65535"
      queryDelimiter=";"
      writeTxnBeginCommand="..."
      writeTxnCommitCommand="..."
    />
  </dbTargets>
</dbTargetConfig>

Please note we tell History Recorder to keep alarm event records in the database for 10 minutes only, and we perform record cleaning every 30 seconds. This only because our SQL Server is not capable of taking heavy load.

5. Restart History Recorder service. Make sure events are now written to CSWorks database (run "select * from HistoricalData" to confirm it).

6. Using cscript tool, run a script that generates 2000 historical data points:

function main()
{
  var areas = 200;
  var dpsInArea = 50;

  for (i = 0; i < areas; i++)
  {
    var areaId = i.toString();
    while(areaId.length < 4)
    {
      areaId = "0" + areaId;
    }

    WScript.Echo("<!-- area 0000" + areaId + "-AAAA-0000-0000-000000000000 -->");

    for (j = 0; j < dpsInArea; j++)
    {
      var dpId = j.toString();
      while(dpId.length < 4)
      {
        dpId = "0" + dpId;
      }
      dpId = areaId + dpId;
      WScript.Echo("<dataPoint id='{00000000-0000-0000-0000-0000" + dpId + "}' description='Tank fill - " + dpId + "' expression='tank1-" + dpId +"'/>");
    }
  }
}

main();

7. Copy generated historical data point descriptions to RecorderDataPoints.xml:

<dataPoints>
  ...
  <dataPoint id='{00000000-0000-0000-0000-000000000000}' description='Tank 1 fill - 0000' expression='tank1-0000'/>
  ...
  <dataPoint id='{00000000-0000-0000-0000-000000001999}' description='Tank 1 fill - 1999' expression='tank1-1999'/>
</dataPoints>

History Recorder will pick up the change in a couple of seconds and will start saving observation for those 2000 data points to the database. Give our setup some time to stabilize.

Results

After 10 minutes, History Recorder maintains about 3.5 million observation records in the database and writes about 6000 observation records every second on average. Database file size is between 1 and 1.5 gigabytes. Here is a screenshot with Performance Monitor and DbgView windows:

Since our test live data changes in a very predictable way, there is a clear pattern in observation recording on the top perfmon chart. History Recorder memory consumption is under control too. Tracing shows that History Recorder deletes between 120000 and 270000 "obsolete" observations every 30 seconds. As you may have noticed, the maximum number of record it is allowed to delete in one shot is 300K, see 'maintenanceCommand' parameter above. Our setup is properly balanced, so History Recorder does not reach this limit.

If you add more historical datapoints and make total count, say 5000, you may end up in a situation when History Recorder simply cannot write all collected observations in a timely manner, and they will accumulate in the memory buffer. Major symptom will be growing memory consumption by History Recorder. CSWorks 1.2.3800.0 introduces "Write Buffer Size" performance counter that shows current number observations to be written to the database by HistoryRecorder, so this overload scenario becomes more obvious.

Summary

Please plan your historical data management carefully. Use scalable database engine, and give it a lot of spare CPU resources. Use multiple History Recorder machines if needed. If the amount of data is extremely big, use multiple databases and apply partitioning technique described in CSWorks documentation.