Offroad Profiling: The Visual Studio Profiler and Windows™ Services


Introduction


The Visual Studio Team System Profiler primarily addresses stand alone application scenarios.  However, more and more modern applications are built around a distributed architecture, and larger and larger portions of functionality are moving into remote services.  Of course, this is nothing new -- multi-tier applications have been the norm for some time - but we in the tools group are increasingly asked to better support these architectures.

What makes this difficult?  Services are not started like normal applications.  Services do not necessarily run under the same account as the user sitting at the console.  Services may run in a completely different Terminal Services session than the current user (in fact, this will likely always be the case when Windows Vista hits the street).


In the Visual Studio IDE, we provide some narrow support for profiling ASP.NET web applications running on the developer desktop.  This is a very special case of service profiling, one where we know a lot about the service process (for example, w3wp.exe on Windows Server 2003) and its behavior, and one we know a majority of people will care about.  For other services, however, IDE support is scant or nonexistent.  In these scenarios you must go off road, using the command line utilities.


In order to understand how to profile services, it will be necessary (or at least helpful) to understand how the profiler itself works.  Over the next few days I'll describe the basics how the profiler collects data from a running application.  With this overview you should be able to understand what contexts break the profiler, and which ones will work.  You should also be able to troubleshoot profiling problems that emerge in your scenario.


As a demonstration, I will provide a simple managed service and client application, and we'll do a walk-through with some simple profiling scenarios.


Understanding Data Collection - Target / Profiler communication


The profiler collects data in a centralized monitor process, VSPerfMon.exe, allowing collection and correlation of data from multiple sources.  Typically, various runtime libraries generate the data that the monitor consumes.  For any type of managed profiling, we load an extension into the CLR.  For trace profiling, instrumentation places an implicit dependency on the trace profiling runtime.  For sample profiling, we load the runtime remotely using CreateRemoteThread.  Additionally, for sample profiling, we also collect data from a software only kernel mode device driver.  Here is a rough sketch of who communicates with whom:


Each of the three user mode runtimes communicates with the monitor over various globally shared objects, such as events, mutices, and memory sections.  These objects are created when the monitor starts, and the runtimes (loaded into the target processes) establish "connections" to the monitor via these shared objects.  To avoid the overhead of attempting a connection on every profiler runtime call, the process typically gets only one opportunity to connect to the monitor.  If this attempt fails, further calls into the profiler runtime are stubbed and produce no useful data.  The implication here is that an instrumented target binary that executes before the monitor initializes will provide no data, and must be restarted.


Getting the Runtimes in


When profiling managed code, we must install an inproc-server to gather various data about the state of the CLR.  The CLR defines a pair of COM interfaces, one implemented by the profiler component by which it receives notifications, and one implemented by the CLR allowing the profiler to query information about the process.


The CLR can only load a profiler component when the process starts.  It decides to do this by checking the COR_ENABLE_PROFILING environment variable.  When set to '1', the CLR will CoCreateClassObject on the class ID specified in the COR_PROFILER environment variable.  In order for the Visual Studio profiler to load its profiling extensions into a process hosting the CLR, these two environment variables must be appropriately set, and the VS Profiler's COM object must be registered.





Note: We've provided a batch file, VSPerfClrEnv.cmd, that will set up the appropriate environment variables.  Also note that services pick up their environment variables from the system defaults.  This batch file can set these as well.

When running a session in sampling mode, and attaching to a process, we use CreateRemoteThread to load the sampling runtime library (appropriately named SamplingRuntime.dll).  CreateRemoteThread has a serious limitation in that it does not work across different Terminal Services sessions.  This means you cannot attach to a process running in another session!  Typically services will run in session zero, and by default, TS sessions will be non-zero, unless you specify the /console flag when connecting.





Note: Do not confuse Terminal Server Session with Logon Session.  Logon Sessions distinguish the security token a process runs with by default.  80 different processes running with 80 different security tokens can all be interacting with the same desktop in the same Terminal Server Session (though this is not a good idea).  Terminal Server Sessions provide a different kind of boundary, most notably separate desktops and distinct instances of csrss.exe.





Note: In Windows Vista, any interactive user session will have a non-zero Terminal Services Session ID.  Yes, this breaks the profiler.  Yes, we are working on a solution to the problem.

Understanding Data Collection - Access issues


The previous discussion has some implications when profiling across two different logon sessions.


First, the monitor always creates the shared objects - it will fail to start if any of its objects are already created.  All objects are created with NULL security attributes, which means they will only be accessible from the account that launched the monitor (modulo an administrator's privilege to take ownership and modify the security descriptor).


What happens when an instrumented application starting in another account attempts to connect to the monitor?  The first profiler runtime call to OpenEvent (or OpenMutex, etc) will fail with ERROR_ACCESS_DENIED.  This will signal that the runtime cannot connect to the monitor, and no data will be collected.  In order to work in this scenario, the target process account must be explicitly granted access to the monitor's objects.  When launching the monitor from the command line, the /USER option allows you to do this.  For instance, if you need an instrumented service to connect to the monitor, and the service runs as LOCAL SERVICE, start the monitor with the following command:



VSPerfCmd /start:trace /output:data.vsp /user:"LOCAL SERVICE"





Note: You may provide multiple /user options if you need to add more than one account.  You may also supply DOMAIN\user for domain accounts. This is an example of a task that the integrated support for ASP.NET profiling in the IDE does automatically.





Note: Be careful when cutting and pasting command lines from web pages. Your browser, or our publishing tool, might encode the characters in some funny manner that causes the command line parsing to go haywire.


Those who have developed services for a while now will immediately ask, "But what if the first profiler runtime code that executes happens while my service is impersonating another user?"  Sadly, the profiler does not accommodate this situation very gracefully.  You're choices are  either to add a /USER option for every user that the service could conceivably impersonate, or make sure your service is connecting to the monitor from a known account.


Getting Started - A sample service and client


A Simple Managed Service


For the purposes of this example I'll use a simple managed service that I built from an application I've been toying with over the last few weeks.  As an amateur (very amateur) cryptography enthusiast, it has always embarrassed me that I've never written a viable cryptogram solver, so I've set out to correct that problem.  As part of my design, I decided that I needed some sort of dictionary to keep tabs on English words, so I built a couple classes around that task. 


Making this "dictionary" a service seemed like a reasonable sample for demonstrating how to profile a service.  For one thing, the scenario isn't entirely fictional - many services take data, do some sort of processing on that data, and return some other data.  This service is no different.  Orthogonally, this process is neither CPU nor I/O bound, which in my opinion represents the real world a little more accurately than samples that just do useless work, like calculating pi.


At the core of this service is the IPatternDictionary interface.  The interface provides two methods (each with an overload).  The first stores a pattern, and the second retrieves a list of words that match a given pattern.  The service implements this dictionary using the fancy new System.Collections.Generic.Dictionary<,> generic collection class, and exposes an IPatternDictionary interface via a remoting channel.>


A Simple Client


On the other side of the system, we'll have a client.  The provided client is a console application that provides two functions.  The client can read a text file, adding each word in the file to the dictionary through IPatternDictionary.AddWord(string word).  Once we've built a nice sized dictionary this way, the client can also query the dictionary for a given pattern using IPatternDictionary.FindPattern(string pattern).  We'll use this client to invoke various operations in the service while profiling.


Run the Example


Source code for the example is at the end of this article.  You may also download the sample code here.






Annoying Disclaimer: This sample code is for the purposes of this demonstration only. There is no guarantee that this code isn't chock full of defects. Don't use this as a base for developing anything you want to use for real -- it has design problems, performance problems, and quite probably security problems as well.

Build the service and client from the supplied solution.  For the sake of profiling, it probably makes the most sense to build the Retail configuration, but for the purposes of this example it isn't important. 


The service itself includes a package installer, so you can use InstallUtil (in %WINDIR%\Microsoft.NET\Frameworks\<version>) to install it.


Once installed, you can start the service from the services snap-in.  If you wish, you may supply a filename from which to serialize / deserialize dictionary data in the "Start Parameters" field in the property page for the service.  If you do not supply a filename, the service starts with an empty dictionary, and discards the dictionary when it shuts down.


With the service started, you can now import data using the client.  There are plenty of large bodies of text available at Project Gutenberg.  To keep up an ironic appearance, I chose "Emma" by Jane Austen (http://www.gutenberg.org/etext/158).  Import the text by running



> cryptoclient import emma11.txt


Connecting to IPatternDictionary
Reading emma11.txt
Added 163682 words


Now the service has a body of words that it knows about.  Let's look for a pattern amongst them.



> cryptoclient find qgabbat


Connecting to IPatternDictionary
Looking up qgabbat
blessed : 7
pressed : 9
guessed : 7
blossom : 1
swelled : 1


If you get this far, then you've now got a working service and client with which to continue.  In the next installment, we'll start profiling.


Profiling the Service


Sampling


First, get the service installed.  Do not start the service; you need to have the magic environment variables set before starting.  To do this, use vsperfclrenv /GlobalSampleOn

> vsperfclrenv /globalsampleon
Enabling VSPerf Global Profiling. Allows to 'attaching' to managed services.
You need to restart the service to detect the new settings. This may require a r
eboot of your machine.

This has the gross side effect of turning on this cursed environment variable for everybody.  Most likely, you will need to reboot. 





Note: I don't need to tell you (and you don't need to tell me) that having to reboot is lame.  I'll have a post about how you can work around this annoyance in the near future.

Start up the monitor in sampling mode.  Make sure you include the service account in the /USER option.

> vsperfcmd /start:sample /output:import_sample.vsp /user:"LOCAL SERVICE"
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.


Now you can start the service.  The environment variables will cause the CLR to load the Visual Studio runtime for managed profiling.  The monitor is now running, and is set up to allow the LOCAL SERVICE account to connect.  You can verify this with the /STATUS option.



> vsperfcmd /status
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.
Process and Thread Status
============================================================
Output File Name  : C:\import_sample.vsp
Maximum Processes: 64
Maximum Threads: 256
Number of Buffers: 258
Size of Buffers: 65536
============================================================
Maximum Number of Processes : 64
Number of Active Processes  : 0
Global Start/Stop Count     : 1
Global Suspend/Resume Count : 0
============================================================
Users with access rights to monitor:
UserName (SID)
NT AUTHORITY\LOCAL SERVICE (S-1-5-19)
SLYTHERIN\AngryRichard (S-1-5-21-1184752212-29216014-527188231-1450159)
NT AUTHORITY\SYSTEM (S-1-5-18)


 Start the service from the services snap-in as usual.  If you're using the provided sample service, most likely it is just sitting there doing nothing, and now would be a good time to attach to it.



> vsperfcmd /attach:PatternDictionaryService.exe
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.
Successfully attached to process ID:2380







Note: Because the service is launched by the Service Control Manager, there isn't any way to use VSPerfCmd /LAUNCH to start the service.  Or is there?  I have an idea for how to work around this, and I hope to share it with you soon.

Now make the service do something useful.



> cryptoclient import emma11.txt
Connecting to IPatternDictionary
Reading emma11.txt
Added 163682 words


If this were all I wanted to profile, I could detach with VSPerfCmd /DETACH.  However, let's pretend I care about what happens to my service all the way to the bitter end, so I'll go ahead and stop the service, then shut down the monitor.



> vsperfcmd /shutdown
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.


Shutting down the Profile Monitor
------------------------------------------------------------


Now I have a VSP file that I can load into the IDE for analysis.  From the IDE, click File->Open->File and select the VSP file you just created.  


I won't go too deeply into data analysis here; it isn't terribly different from analyzing data from an application, and there's nothing too surprising to see in this example.  I will point out, though, that when sampling a scenario like this, you will see a lot of noise related to marshalling, remoting, serialization, and all that tasty goo the CLR gives you to make distributed application development easier.  I've found that a good way to drill through all this gunk is to start from the Function View, right click and choose "Group By Module". 





Note: In the July CTP and later, "Group by Module" will be the default in the Function View

Now you can get started looking at your own code, rather than frameworks methods with really long names.


Trace


Trace profiling a service is only slightly different from sample profiling.  First of all, and most importantly, the Visual Studio profiler's CLR component uses a different CLSID for trace profiling, so once again it's necessary to set up the environment properly, and reboot the system:



> vsperfclrenv /globaltraceon
Enabling VSPerf Global Profiling. Allows trace profiling of managed services without allocation profiling. You need to restart the service to detect the new settings. This may require a reboot of your machine.


When trace profiling, you must instrument the code you wish to profile.  The example service uses two assemblies: PatternDictionaryService.exe and CommonTypes.dll.  For this example, I'll instrument both.



> vsinstr PatternDictionaryService.exe


Microsoft (R) VSInstr Post-Link Instrumentation 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.


File to Process:
   C:\PatternDictionaryService.exe --> C:\PatternDictionaryService.exe
Original file backed up to C:\PatternDictionaryService.orig.exe Successfully instrumented file C:\PatternDictionaryService.exe.


> vsinstr commontypes.dll
Microsoft (R) VSInstr Post-Link Instrumentation 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved. File to Process:
   C:\commontypes.dll --> C:\commontypes.dll
Original file backed up to C:\commontypes.orig.dll Successfully instrumented file C:\commontypes.dll.


Start the monitor.  Remember, the monitor must be started before the trace runtime attempts to connect.  The trace runtime will attempt to connect when the service is started.



> vsperfcmd /start:trace /output:find_trace.vsp
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.


Start the service, just like normal.  When I ran this example, I supplied the same file name as in the Sampling example, so that I'd be profiling the work to deserialize the dictionary. You can verify that the trace runtime connected with the monitor using the STATUS option.  When trace profiling, the monitor also tracks threads, so you'll get a bunch of output, which I've pruned a little.



> vsperfcmd /status
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.
Process and Thread Status
============================================================
Output File Name  : C:\find_trace.vsp
Maximum Processes: 64
Maximum Threads: 256
Number of Buffers: 258
Size of Buffers: 65536
============================================================
Maximum Number of Processes : 64
Number of Active Processes  : 1
Global Start/Stop Count     : 1
Global Suspend/Resume Count : 0
------------------------------------------------------------
 Process              :  C:\PatternDictionaryService.exe
 Process ID           : 3768
 Num Threads          : 13
 Start/Stop Count     : 1
 Suspend/Resume Count : 0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   Thread               :  T@ 04B5B450:12
   Thread ID            : 2456
   Start/Stop Count     : 1
   Suspend/Resume Count : 0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   Thread               :  T@ 04B5B768:13
   Thread ID            : 2456
   Start/Stop Count     : 1
   Suspend/Resume Count : 0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[snippage]



Users with access rights to monitor:
UserName (SID)
SLYTHERIN\AngryRichard (S-1-5-21-1847212521-1276092920-1812408775-1514059)
NT AUTHORITY\SYSTEM (S-1-5-18)






Note: You'll notice that I didn't add LOCAL SERVICE to the monitor.  While working on this article, I had to move the sample project to a different machine, and somewhere in the shuffle reverted the service to running as SYSTEM.  A service running as SYSTEM doesn't need any help from you to get at the monitor.  It is SYSTEM, after all.


Invoke the service.  This time I'll find a pattern in the dictionary.



> cryptoclient find bhhnq
Connecting to IPatternDictionary
Looking up bhhnq
books : 9
apply : 4
allow : 48
alloy : 3
looks : 29
ennui : 1
weeks : 21
abbey : 31
apple : 8
upper : 3
goose : 2
feels : 10
doors : 12
occur : 10
offer : 19
fools : 2
affix : 1
pools : 1
rooms : 15
issue : 2
needs : 2
utter : 4
essay : 1
arrow : 1
meets : 1
woods : 1


Stop the service, then stop the monitor. 



> vsperfcmd /shutdown
Microsoft (R) VSPerf Command Version 8.0.50215 x86
Copyright (C) Microsoft Corp. All rights reserved.




Shutting down the Profile Monitor
------------------------------------------------------------


Again, you'll have a VSP file that you can view in Visual Studio.


Nothing too surprising here in the call stack, although do notice that with trace, since we start collecting the moment the service starts, we can see into the OnStart method if we cared to. 


Epilogue


The examples above should give you enough information to get started using the profiler on your service.  Though admittedly inelegant to get running, you will get data by doing this, and I hope you get data that helps you optimize your applications.

I've alluded to a number of future topics in this series, which I'll recap here:


  • CLR Profiling Environment Variables
    How to get those pesky environment variables into your service (and just your service) without rebooting the whole machine


  • Launching a Service for Sampling
    A somewhat brutal workaround to enable the logical equivalent to VSPerfCmd /launch:foo.exe


  • Cross Session Madness
    How to attach the sampler to a process in another TS session


  • Controlling the Data Flood
    How to use the Profiler Control API to focus on interesting parts of your service
    I touched on this already in this post.


  • Examining Client / Server Interaction
    How to profile the service and the client simultaneously, and look at the interaction between the two 

 


Source Code for Sample


CommonTypes.dll


CommonTypes.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 */
using System;
using System.Collections.Generic;
using System.Text;


namespace Cryptogram
{
    namespace CommonTypes
    {
        /// <summary>
        /// Every word in a dictionary has data associated with it.
        /// For now, all we care about is relative frequency in the english
        /// language, but it's conceivable that semantics could be encoded
        /// here.
        /// </summary>
        [System.Serializable]
        public struct WordProperties
        {
            private string Word_;
            private int Frequency_;


            public string Word
            {
                get { return Word_; }
            }


            public int Frequency
            {
                get { return Frequency_; }
                set { Frequency_ = value; }
            }


            public WordProperties(string word, int frequency)
            {
                this.Word_ = word.ToLower();
                this.Frequency_ = frequency;
            }
        }


        /// <summary>
        /// A Pattern is simply a canonical representation of a word by its
        /// unique characters.  Any word is a non-canonical pattern, but
        /// a *canonical* pattern has special form:
        ///
        ///  * The first character is always 'A'
        ///  * Further characters must either be a prior character, or
        ///    the successor character to a prior character.
        ///
        /// For example, 'aaaa', 'abcd', 'aaba' are all valid canonical
        /// patterns.  'baaa', 'acaa', 'acba' are *not* valid canonical
        /// patterns.
        ///
        /// We "canonicalize" a pattern by substituting the first character
        /// with 'a'.  Other characters in the pattern that match the first
        /// character are also replaced with 'a'.  We then proceed to the
        /// next unique character and replace it with 'b'.  This continues
        /// until the word is fully canonicalized.
        ///
        /// For example, the word 'canonical' translates to the canonical
        /// pattern 'abcdceabf'.
        /// </summary>
        [System.Serializable]
        public struct Pattern
        {
            private String Pattern_;


            public override String ToString()
            {
                return Pattern_;
            }


            public override int GetHashCode()
            {
                return Pattern_.GetHashCode();
            }


            public Pattern(String word)
            {
                // Case insensitive
                word.ToLower();


                // Create a string with just dots, same length as the original word.
                char[] pattern = new char[word.Length];
                for (int i = 0; i < word.Length; ++i)
                    pattern[i] = '.';


                // For each letter in the word
                char t = 'a';
                for (int i = 0; i < word.Length; ++i)
                {
                    // If the pattern already has a token, we've already taken care
                    // of this one.
                    if (pattern[i] != '.')
                        continue;


                    // Need to assign a token for this guy.
                    int j = 0;
                    while ((j = word.IndexOf(word[i], j)) != -1)
                    {
                        pattern[j] = t;
                        ++j;
                    }
                    ++t;
                }


                Pattern_ = new String(pattern);
            }
        }


        /// <summary>
        /// Objects that expose the pattern dictionary interface provide a
        /// mechanism for clients to add to the corpus of word properties,
        /// along with a mechanism to recover word collections matching a
        /// given pattern.
        /// </summary>
        public interface IPatternDictionary
        {
            /// <summary>
            /// Adds a word to the corpus.  The word is assigned a default
            /// frequency of '1'.  If the corpus already contains the word,
            /// the corpus' frequency for the word is incremented by '1'.
            /// </summary>
            /// <param name="word">
            /// The word to add to the corpus.
            /// </param>
            void AddWord(string word);


            /// <summary>
            /// Adds given word properties to the corpus.  If the corpus
            /// already contains the word, the frequencies are combined.
            /// </summary>
            /// <param name="wordProperties">
            /// Properties of the word to be added.
            /// </param>
            void AddWord(WordProperties wordProperties);


            /// <summary>
            /// Retrieves a list of word properties for the given pattern.
            /// </summary>
            /// <param name="pattern">
            /// A pattern to search for in the corpus.  The pattern need
            /// not be canonical.
            /// </param>
            /// <returns>
            /// A list of WordProperties matching the given pattern
            /// </returns>
            List<WordProperties> Lookup(string pattern);


            /// <summary>
            /// Retrieves a list of word properties for the given pattern.
            /// </summary>
            /// <param name="canonicalPattern">
            /// A pattern to search for in the corpus.  The pattern must
            /// be canonical.
            /// </param>
            /// <returns>
            /// A list of WordProperties matching the given pattern
            /// </returns>
            List<WordProperties> Lookup(Pattern canonicalPattern);
        }
    }


PatternDictionaryService.exe


Installer.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 */
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;


namespace PatternDictionaryService
{
    [RunInstaller(true)]
    public class PatternDictionaryServiceInstaller : Installer
    {
        private ServiceProcessInstaller ServiceProcessInstaller_;
        private ServiceInstaller ServiceInstaller_;


        public PatternDictionaryServiceInstaller()
        {
            ServiceInstaller_ = new ServiceInstaller();
            ServiceInstaller_.ServiceName = "PatternDictionaryService";


            ServiceProcessInstaller_ = new ServiceProcessInstaller();
            ServiceProcessInstaller_.Account = ServiceAccount.LocalSystem;


            Installers.AddRange(
                new Installer[] {
                    ServiceProcessInstaller_,
                    ServiceInstaller_
                });
        }
    }
}


PatternDictionaryImpl.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 */
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;


using Cryptogram.CommonTypes;


namespace PatternDictionaryService
{
    class PatternDictionaryImpl : MarshalByRefObject, IPatternDictionary
    {
        private static PatternDictionary patternDictionary;


        public static void Init(string fileName)
        {
            if (fileName != null)
            {
                try
                {
                    patternDictionary = PatternDictionary.CreateFromDictionaryFile(fileName);
                }
                catch (System.IO.FileNotFoundException)
                {
                    patternDictionary = new PatternDictionary();
                }
            }
            else
            {
                patternDictionary = new PatternDictionary();
            }
        }


        public static void Shutdown(string fileName)
        {
            if (fileName == null)
                return;


            if (patternDictionary == null)
                return;


            if (File.Exists(fileName))
                File.Delete(fileName);


            patternDictionary.ExportToFile(fileName);
        }


        //
        // IPatternDictionary
        //
        public void AddWord(string word)
        {
            patternDictionary.AddWord(new WordProperties(word, 1));
        }


        public void AddWord(WordProperties wordProperties)
        {
            patternDictionary.AddWord(wordProperties);
        }


        public List<WordProperties> Lookup(string pattern)
        {
            return patternDictionary.Lookup(new Pattern(pattern));
        }


        public List<WordProperties> Lookup(Pattern canonicalPattern)
        {
            return patternDictionary.Lookup(canonicalPattern);
        }
    }


    [System.Serializable]
    public class PatternDictionary : Dictionary<Pattern, Dictionary<string, WordProperties>>
    {
        public PatternDictionary()
        {
        }


        public void AddWord(WordProperties wordProperties)
        {
            string word = wordProperties.Word;
            Pattern p = new Pattern(word);


            Dictionary<string, WordProperties> wordList;


            if (this.ContainsKey(p))
            {
                wordList = this[p];
            }
            else
            {
                wordList = new Dictionary<string, WordProperties>();
                this.Add(p, wordList);
            }


            if (wordList.ContainsKey(word))
            {
                int frequency = wordList[word].Frequency + wordProperties.Frequency;


                wordList[word] = new WordProperties(word, frequency);
            }
            else
            {
                wordList.Add(word, wordProperties);
            }
        }


        public List<WordProperties> Lookup(Pattern canonicalPattern)
        {
            EventLog.WriteEntry("PatternDictionaryService",
                String.Format("IPatternDictionary.Lookup({0})", canonicalPattern.ToString()));


            List<WordProperties> result = new List<WordProperties>();


            if (this.ContainsKey(canonicalPattern))
            {
                foreach (WordProperties wordProperties in this[canonicalPattern].Values)
                {
                    result.Add(wordProperties);
                }
            }


            return result;
        }


        // Writes dictionary to a file.
        public void ExportToFile(String fileName)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream fileStream = new FileStream(fileName, FileMode.CreateNew))
            {
                formatter.Serialize(fileStream, this);
            }
        }


        // A factory to deserialize from a file.
        public static PatternDictionary CreateFromDictionaryFile(String fileName)
        {
            PatternDictionary patternDictionary;


            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
            {
                patternDictionary = (PatternDictionary)formatter.Deserialize(fileStream);
            }


            return patternDictionary;
        }


        // Deserializing constructor
        protected PatternDictionary(SerializationInfo info, StreamingContext context)
: base(info, context)
        {
        }
    }
}


PatternDictionaryService.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting;
using System.Text;


namespace PatternDictionaryService
{
    public class PatternDictionaryService : ServiceBase
    {
        /// <summary>
        /// Persist the startup file name, so that when we shut down,
        /// we can serialize updated data back to the file.
        /// </summary>
        private string FileName_;


        /// <summary>
        /// Channel that exposes IPatternDictionary
        /// </summary>
        TcpChannel RemoteChannel_;



        /// <summary>
        /// Initialize junk here.
        /// </summary>
        public PatternDictionaryService()
        {
            FileName_ = null;
        }


        /// <summary>
        /// Called when the service starts
        /// </summary>
        /// <param name="args">
        /// The first element of this array is an optional filename for
        /// recovering or storing dictionary data.
        /// </param>
        protected override void OnStart(string[] args)
        {
            EventLog.WriteEntry("PatternDictionaryService", "OnStart");


            //
            // If a file name is provided, we deserialize from that file.
            //
            if (args.Length > 0)
            {
                FileName_ = args[0];


                EventLog.WriteEntry("PatternDictionaryService",
                                    String.Format("Deserializing from {0}", FileName_));
               
                PatternDictionaryImpl.Init(FileName_);
            }
            else
            {
                FileName_ = null;


                EventLog.WriteEntry("PatternDictionaryService",
                                    "Starting with empty pattern dictionary");
                PatternDictionaryImpl.Init(null);
            }


            // Create a channel for remoting IPatternDictionary.
            RemoteChannel_ = new TcpChannel(8080);
            ChannelServices.RegisterChannel(RemoteChannel_);
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(PatternDictionaryImpl),
                "IPatternDictionary",
                WellKnownObjectMode.SingleCall);
        }


        /// <summary>
        /// Called when the service is stopped.
        /// </summary>
        protected override void OnStop()
        {
            EventLog.WriteEntry("PatternDictionaryService", "OnStop");


            if (RemoteChannel_ != null)
            {
                ChannelServices.UnregisterChannel(RemoteChannel_);
                RemoteChannel_ = null;
            }
           
            PatternDictionaryImpl.Shutdown(FileName_);
        }
    }
}


Program.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 */
using System.Collections.Generic;
using System.ServiceProcess;
using System.Text;


namespace PatternDictionaryService
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main()
        {
            ServiceBase[] ServicesToRun;


            // More than one user Service may run within the same process. To add
            // another service to this process, change the following line to
            // create a second service object. For example,
            //
            //   ServicesToRun = new ServiceBase[] {new Service1(), new MySecondUserService()};
            //
            ServicesToRun = new ServiceBase[] { new PatternDictionaryService() };


            ServiceBase.Run(ServicesToRun);
        }
    }


CryptoClient.exe


Program.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;


using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting;


using Cryptogram.CommonTypes;


namespace CryptoClient
{
    class Program
    {
        static void Usage()
        {
            Console.Out.WriteLine("Usage:");
            Console.Out.WriteLine("");
            Console.Out.WriteLine("    CryptoClient [command [args]]");
            Console.Out.WriteLine("");
            Console.Out.WriteLine("    Where [command] is:");
            Console.Out.WriteLine("        Import (filename)");
            Console.Out.WriteLine("        Find   (pattern)");
        }


        static int Find(string pattern)
        {
            Console.Out.WriteLine("Connecting to IPatternDictionary");


            //
            // Get interface to pattern service
            //
            IPatternDictionary a =
                (IPatternDictionary)Activator.GetObject(
                                        typeof(IPatternDictionary),
                                        "tcp://localhost:8080/IPatternDictionary");


            List<WordProperties> results;


            Console.Out.WriteLine("Looking up {0}", pattern);


            results = a.Lookup(pattern);


            foreach (WordProperties properties in results)
            {
                Console.Out.WriteLine("{0} : {1}", properties.Word, properties.Frequency);
            }


            return 0;
        }


        static int Import(string fileName)
        {
            Console.Out.WriteLine("Connecting to IPatternDictionary");


            //
            // Get interface to pattern service
            //
            IPatternDictionary a =
                (IPatternDictionary)Activator.GetObject(
                                        typeof(IPatternDictionary),
                                        "tcp://localhost:8080/IPatternDictionary");


            Console.Out.WriteLine("Reading {0}", fileName);
            int wordCount = 0;


            //
            // Create file stream from file name.
            //
            using (FileStream fs = File.OpenRead(fileName))
            {
                using (StreamReader sr = new StreamReader(fs))
                {
                    WordStream ws = new WordStream(sr);
                    String word;


                    //
                    // Add each word in the file to the dictionary.
                    //
                    while ((word = ws.Get()) != null)
                    {
                        a.AddWord(word);
                        ++wordCount;
                    }
                }
            }


            Console.Out.WriteLine("Added {0} words", wordCount);


            return 1;
        }
       
        static int Main(string[] args)
        {
            if (args.Length == 0)
            {
                Usage();
                return 0;
            }


            TcpChannel channel = new TcpChannel();
            ChannelServices.RegisterChannel(channel);


            string command = args[0];


            if (command.Equals("Import", StringComparison.OrdinalIgnoreCase))
            {
                if (args.Length < 2)
                {
                    Usage();
                    return 1;
                }


                string fileName = args[1];
                return Import(fileName);
            }


            if (command.Equals("Find", StringComparison.OrdinalIgnoreCase))
            {
                if (args.Length < 2)
                {
                    Usage();
                    return 1;
                }


                string pattern = args[1];
                return Find(pattern);
            }


            return 1;
        }
    }


WordStream.cs






/*
 * Copyright (c) 2005 Microsoft Corporation
 * All rights reserved.  No warrantees expressed or implied.
 *
 * WordStream class library.  Produces a stream of words from a text stream.
 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;


using Cryptogram.CommonTypes;


namespace CryptoClient
{
    public class WordStream
    {


#region(PrivateData)


        private StreamReader Stream_;
        private MatchCollection Matches_;
        private int MatchIndex_;
        private Regex Regex_;


#endregion


        //
        // WordStream is constructed from an existing stream reader.
        //
        public WordStream(StreamReader stream)
        {
            Stream_ = stream;
            Matches_ = null;
            MatchIndex_ = 0;
            Regex_ = new Regex("\\w*");
        }


        //
        // Return a word from the stream.
        //
        public String Get()
        {
            if (Stream_ == null)
                return null;


            while (true)
            {
                // If no current matches, read a line and match it.
                if (Matches_ == null)
                {
                    String line = Stream_.ReadLine();
                    if (line == null)
                        return null;


                    Matches_ = Regex_.Matches(line);
                    MatchIndex_ = 0;
                }


                if (MatchIndex_ < Matches_.Count)
                {
                    Match m = Matches_[MatchIndex_];
                    ++MatchIndex_;
                    String word = m.ToString();
                    if (word.Length > 0)
                        return m.ToString();
                }
                else
                {
                    // Go around again.
                    Matches_ = null;
                }
            }
        }


    }

Comments (8)
  1. It’s one of the most frequently asked internal support questions.&amp;nbsp; This should help with the learning…

  2. Eric Jarvi says:

    If you want to run the performance profiler on your managed windows services, this is a must read:

    http://blogs.msdn.com/angryrichard/articles/Profiling_Windows_Services.aspx

  3. Eric Jarvi says:

    If you want to run the performance profiler on your managed windows services, this is a must read:

    http://blogs.msdn.com/angryrichard/articles/Profiling_Windows_Services.aspx

  4. Adam Singer – Cause and effect

    Adam gives an excellent post on how all the different version control…

  5. The profiler team has been crazy busy with getting Visual Studio Team System shipped and out the door,…

  6. I just recently whipped up a FAQ on the profiler&amp;nbsp;to post as a sticky thread in the VSTS for Developers…

Comments are closed.

Skip to main content