MSSQLWIKI

Karthick P.K on SQL Server

Archive for November, 2010

How to find SQL Server and system CPU usage history :

Posted by Karthick P.K on November 30, 2010

SQL Server and system CPU usage history can be obtained from sys.dm_os_ring_buffers  using below query

Note: For troubleshooting  high CPU usage in SQL Server follow https://mssqlwiki.com/2012/10/04/troubleshooting-sql-server-high-cpu-usage/

SQL Server 2005

DECLARE @ts_now bigint

SELECT @ts_now = cpu_ticks / CONVERT (float, cpu_ticks_in_ms) FROM sys.dm_os_sys_info

SELECT top 20 record_id, EventTime, 

  CASE WHEN system_cpu_utilization_post_sp2 IS NOT NULL THEN system_cpu_utilization_post_sp2 ELSE system_cpu_utilization_pre_sp2 END AS system_cpu_utilization, 

  CASE WHEN sql_cpu_utilization_post_sp2 IS NOT NULL THEN sql_cpu_utilization_post_sp2 ELSE sql_cpu_utilization_pre_sp2 END AS sql_cpu_utilization

FROM 

(

  SELECT 

    record.value('(Record/@id)[1]', 'int') AS record_id,

    DATEADD (ms, -1 * (@ts_now - [timestamp]), GETDATE()) AS EventTime,

    100-record.value('(Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS system_cpu_utilization_post_sp2,

    record.value('(Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS sql_cpu_utilization_post_sp2 , 

    100-record.value('(Record/SchedluerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS system_cpu_utilization_pre_sp2,

    record.value('(Record/SchedluerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS sql_cpu_utilization_pre_sp2

  FROM (

    SELECT timestamp, CONVERT (xml, record) AS record 

    FROM sys.dm_os_ring_buffers 

    WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'

      AND record LIKE '%<SystemHealth>%') AS t

) AS t

ORDER BY record_id desc

 

SQL Server 2008

DECLARE @ts_now bigint

SELECT @ts_now = cpu_ticks / (cpu_ticks/ms_ticks)  FROM sys.dm_os_sys_info

SELECT top 20 record_id, EventTime, 

  CASE WHEN system_cpu_utilization_post_sp2 IS NOT NULL THEN system_cpu_utilization_post_sp2 ELSE system_cpu_utilization_pre_sp2 END AS system_cpu_utilization, 

  CASE WHEN sql_cpu_utilization_post_sp2 IS NOT NULL THEN sql_cpu_utilization_post_sp2 ELSE sql_cpu_utilization_pre_sp2 END AS sql_cpu_utilization

FROM 

(

  SELECT 

    record.value('(Record/@id)[1]', 'int') AS record_id,

    DATEADD (ms, -1 * (@ts_now - [timestamp]), GETDATE()) AS EventTime,

    100-record.value('(Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS system_cpu_utilization_post_sp2,

    record.value('(Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS sql_cpu_utilization_post_sp2 , 

    100-record.value('(Record/SchedluerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS system_cpu_utilization_pre_sp2,

    record.value('(Record/SchedluerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS sql_cpu_utilization_pre_sp2

  FROM (

    SELECT timestamp, CONVERT (xml, record) AS record 

    FROM sys.dm_os_ring_buffers 

    WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'

      AND record LIKE '%<SystemHealth>%') AS t

) AS t

ORDER BY record_id desc

Posted in Performance, SQL Query | Tagged: , , , , , | 16 Comments »

The database ‘model’ is marked RESTORING and is in a state that does not allow recovery to be run.Could not create tempdb. You may not have enough disk space available. Free additional disk space by deleting other files on the tempdb drive

Posted by Karthick P.K on November 25, 2010

The database ‘model’ is marked RESTORING and is in a state that does not allow recovery to be run.Could not create tempdb. You may not have enough disk space available. Free additional disk space by deleting other files on the tempdb drive

Error:

The database ‘model’ is marked RESTORING and is in a state that does not allow recovery to be run.

Error: 927, Severity: 14, State: 2.

Database ‘model’ cannot be opened. It is in the middle of a restore.

Could not create tempdb. You may not have enough disk space available. Free additional disk space by deleting other files on the tempdb drive and then restart SQL Server. Check for additional errors in the event log that may indicate why the tempdb files could not be initialized.

 

Cause

We get above error if the Model database is corrupted. TempDB is recreated every time when SQLServer restarts using model database, We receive "Could not create tempdb" because model database is corrupted

Resolution

1. Start SQLServer with Traceflag 3608,3609,-c,-f

-T3609 Will keep the existing TEMPDB, Which means when SQLServer is restarted SQLServer uses the existing tempdb instead of re-creating it as long as checkpoint in the tempdb had been done immediately before the last server shutdown

{

Sqlservr.exe -sInstanceName -T3608 -c -f -T3609

}

2. Open SQLCMD and make DAC connection

{

SQLCMD -E -SADMIN:Servername\InstanceName

}

3. Use Tempdb

Go

{

Above command will open the TempDB. If you get error while executing "Use Tempdb" your tempdb is not cleanly shutdown.

To work around this copy a Tempdb.mdf and templog.ldf from Cleanly shutdown SQLServer of same version and replace it in TEMPDB location.

Also note Transaction log location is stored in Tempdb.mdf.So you may have to copy Tlog file in destination server in same directory structure as it existed source server .

Once you copy the file restart SQLServer using -T3608 -c -f -T3609 Then run

Use Tempdb

Go

}

4. sp_detach_db ‘model

5. Replace model.mdf and model.ldf from different server of same build

5. sp_attach_db ‘model’,’X:\PAth\model.mdf’,’x:\modellog.ldf’

{

Note: If you are in SQLServer2008 we can use "create database with attach" option

 CREATE DATABASE [model] ON
 ( FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.R2\MSSQL\DATA\model.mdf' ),
 ( FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.R2\MSSQL\DATA\modellog.ldf' )
 FOR ATTACH
 go

}

Now we can start SQL Server normally and if you find any database in restoring state if can use RESTORE database DBNAME WITH RECOVERY to recover and open the database.

 

 

Thank You and Best Regards,

Karthick P.K

Posted in Recovery, Startup failures | Tagged: , , , , , , , , , , | 24 Comments »

Script to get current blocking tree with wait types

Posted by Karthick P.K on November 24, 2010

 

 

SET NOCOUNT ON; 

SET CONCAT_NULL_YIELDS_NULL OFF 

GO 

WITH BLOCKERS (SPID, BLOCKED, LEVEL, BATCH,waittype,lastwaittype) 

AS 

( 

   SELECT 

   SPID, 

   BLOCKED, 

   CAST (REPLICATE ('0', 4-LEN (CAST (SPID AS VARCHAR))) + CAST (SPID AS VARCHAR) AS VARCHAR (1000)) AS LEVEL, 

   REPLACE (REPLACE (T.TEXT, CHAR(10), ' '), CHAR (13), ' ' ) AS BATCH, 

   R.waittype, 

   R.lastwaittype 

   FROM sys.sysprocesses R with (nolock) 

   CROSS APPLY SYS.DM_EXEC_SQL_TEXT(R.SQL_HANDLE) T 

   WHERE (BLOCKED = 0 OR BLOCKED = SPID) 

   AND EXISTS    (SELECT SPID,BLOCKED,CAST (REPLICATE ('0', 4-LEN (CAST (SPID AS VARCHAR))) + CAST (SPID AS VARCHAR) AS VARCHAR (1000)) AS LEVEL, 

   BLOCKED, REPLACE (REPLACE (T.TEXT, CHAR(10), ' '), CHAR (13), ' ' ) AS BATCH,R.waittype,R.lastwaittype FROM sys.sysprocesses R2 with (nolock) 

   CROSS APPLY SYS.DM_EXEC_SQL_TEXT(R.SQL_HANDLE) T 

WHERE R2.BLOCKED = R.SPID AND R2.BLOCKED <> R2.SPID) 

 

UNION ALL 

 

SELECT 

    R.SPID, 

    R.BLOCKED, 

    CAST (BLOCKERS.LEVEL + RIGHT (CAST ((1000 + R.SPID) AS VARCHAR (100)), 4) AS VARCHAR (1000)) AS LEVEL, 

    REPLACE (REPLACE (T.TEXT, CHAR(10), ' '), CHAR (13), ' ' ) AS BATCH, 

    R.waittype, 

    R.lastwaittype 

    FROM sys.sysprocesses AS R with (nolock) 

    CROSS APPLY SYS.DM_EXEC_SQL_TEXT(R.SQL_HANDLE) T 

    INNER JOIN BLOCKERS ON R.BLOCKED = BLOCKERS.SPID WHERE R.BLOCKED > 0 AND R.BLOCKED <> R.SPID 

) 

 

SELECT N'       ' + REPLICATE (N'|      ', LEN (LEVEL)/4 - 2) + CASE WHEN (LEN (LEVEL)/4 - 1) = 0 THEN 'HEAD - ' ELSE '|------ ' END + CAST (SPID AS VARCHAR (10)) + ' '  + BATCH AS BLOCKING_TREE ,  waittype ,lastwaittype,  GETDATE() as Time FROM BLOCKERS with (nolock) ORDER BY LEVEL ASC 

go

 

–By Ajith Krishnan

Posted in Performance, SQL Query | Tagged: , , , , , , , , , | 6 Comments »

SQL Server2008/SQL Server2012: Script level upgrade for database ‘master’ failed because upgrade step ‘sqlagent100_msdb_upgrade.sql’ encountered error 574, state 0, severity 16

Posted by Karthick P.K on November 17, 2010

SQL Server 2008 : Script level upgrade for database ‘master’ failed because upgrade step ‘sqlagent100_msdb_upgrade.sql’ encountered error 574, state 0, severity 16

SQL Server 2012 : Script level upgrade for database ‘master’ failed because upgrade step ‘msdb110_upgrade.sql’  encountered error

SQL Server 2008/2012 instance fails to start or hangs after service pack or Cumulative update installation.

 

Error

Script level upgrade for database ‘master’ failed because upgrade step ‘sqlagent100_msdb_upgrade.sql’ encountered error 574, state 0, severity 16. This is a serious error condition which might interfere with regular operation and the database will be taken offline. If the error happened during upgrade of the ‘master’ database, it will prevent the entire SQL Server instance from starting. Examine the previous errorlog entries for errors, take the appropriate corrective actions and re-start the database so that the script upgrade steps run to completion.

 

 

Error: 574, Severity: 16, State: 0.

CONFIG statement cannot be used inside a user transaction.

Error: 912, Severity: 21, State: 2.

Script level upgrade for database ‘master’ failed because upgrade step ‘sqlagent100_msdb_upgrade.sql’ encountered error 574, state 0, severity 16. This is a serious error condition which might interfere with regular operation and the database will be taken offline. If the error happened during upgrade of the ‘master’ database, it will prevent the entire SQL Server instance from starting. Examine the previous errorlog entries for errors, take the appropriate corrective actions and re-start the database so that the script upgrade steps run to completion.

Error: 3417, Severity: 21, State: 3.

Script level upgrade for database ‘master’ failed because upgrade step ‘msdb110_upgrade.sql’ encountered error 15173, state 1, severity 16

Start SQL Server from command prompt using trace flag –T902 to disable script execution

1.Turn off Implicit transaction

{

EXEC sys.sp_configure N’user options’, N’0′

GO

RECONFIGURE WITH OVERRIDE

GO

}

2. SQL Server not able to create temp_MS_AgentSigningCertificate_database.mdf

Error:

{

Directory lookup for the file "P:\Data\temp_MS_AgentSigningCertificate_database.mdf" failed with the operating system error 2(The system cannot find the file specified.).

Error: 1802, Severity: 16, State: 1.

CREATE DATABASE failed. Some file names listed could not be created. Check related errors.

spid7s Error: 912, Severity: 21, State: 2.

spid7s Script level upgrade for database ‘master’ failed because upgrade step ‘sqlagent100_msdb_upgrade.sql’ encountered error 598, state 1, severity 25.

CREATE FILE encountered operating system error 3(The system cannot find the path specified.) while attempting to open or create the physical file ‘Q:\Data\temp_MS_AgentSigningCertificate_database_log.LDF’.

}

This error is raised when the default database location is invalid. Edit below registry to have a valid directory for default database location.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10.<Instance Name>\Setup\SQLDataRoot

3. Check if there are Orphan users in system databases and fix them.

{

EXEC sp_change_users_login ‘Report’;

}

4. If you see “error 15173, state 1, severity 16”

Ex: Script level upgrade for database ‘master’ failed because upgrade step ‘msdb110_upgrade.sql’ encountered error 15173, state 1, severity 16

Revoke the permissions granted on ‘##MS_PolicyEventProcessingLogin##’

you can use the below script to identify the users who have permissions granted on ‘##MS_PolicyEventProcessingLogin##’

select a.name,b.permission_name from sys.server_principals a,sys.server_permissions b,sys.server_principals c

where a.principal_id= b.grantee_principal_id and b.grantor_principal_id=c.principal_id and c.name = ‘##MS_PolicyEventProcessingLogin##’

 

Resolution

If none of the above resolves the issue then you can use Trace flag -T3601 which causes the first 512 characters of each batch being executed to be printed to the error log. Identify the batch which is failing and troubleshoot the batch.

 

If you liked this post, do like us on FaceBook at https://www.facebook.com/mssqlwiki and join our FaceBook group https://www.facebook.com/mssqlwiki#!/groups/454762937884205/

 

 

Thank you,

Karthick P.K | My Facebook Page |My Site| Blog space| Twitter

Disclaimer:

The views expressed on this website/blog are mine alone and do not reflect the views of my company. All postings on this blog are provided “AS IS” with no warranties, and confers no rights.

Posted in SQL Cluster Setup, SQL Server Setup, Startup failures | Tagged: , , , , , , , , , , , | 14 Comments »

AWE allocator API’s (How SQL Server AWE works)

Posted by Karthick P.K on November 11, 2010

#include <windows.h> 
#include <string> 
#include <winbase.h> 
#include <iostream> 
using namespace std;
#include <psapi.h>
#pragma comment(lib,"psapi.lib")


BOOL LoggedSetLockPagesPrivilege ( HANDLE hProcess,BOOL bEnable)
{
  struct {
    DWORD Count;
    LUID_AND_ATTRIBUTES Privilege [1];
  } Info;

  HANDLE Token;
  BOOL Result;

  // Open the token.

  Result = OpenProcessToken ( hProcess,
                              TOKEN_ADJUST_PRIVILEGES,
                              & Token);

  if( Result != TRUE ) 
  {
    printf( "Cannot open process token.\n" );
    return FALSE;
  }

  // Enable or disable?

  Info.Count = 1;
  if( bEnable ) 
  {
    Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
  } 
  else 
  {
    Info.Privilege[0].Attributes = 0;
  }

  // Get the LUID.

  Result = LookupPrivilegeValue ( NULL,
                                  SE_LOCK_MEMORY_NAME,
                                  &(Info.Privilege[0].Luid));

  if( Result != TRUE ) 
  {
    printf( "Cannot get privilege for %s.\n", SE_LOCK_MEMORY_NAME );
    return FALSE;
  }

  // Adjust the privilege.

  Result = AdjustTokenPrivileges ( Token, FALSE,
                                   (PTOKEN_PRIVILEGES) &Info,
                                   0, NULL, NULL);

  // Check the result.

  if( Result != TRUE ) 
  {
    printf ("Cannot adjust token privileges (%u)\n", GetLastError() );
    return FALSE;
  } 
  else 
  {
    if( GetLastError() != ERROR_SUCCESS ) 
    {
      printf ("Cannot enable the SE_LOCK_MEMORY_NAME privilege; ");
      printf ("please check the local policy.\n");
      return FALSE;
    }
  }

  CloseHandle( Token );

  return TRUE;
};






void main()
    {

    long int s=0;
    
    printf("\nEnter the size in MB  for address range that can be used to map Address Windowing Extensions (AWE) pages:");
    scanf("%d",&s);
    LPVOID lpaddress=NULL; 
    SIZE_T size=s*1024*1024;  //size in bytes
    printf ("\n%d",size);
    LPVOID ADD;
    int i;
    char *ADDw;
    BOOL bResult= FALSE;
    BOOL bResult2= FALSE;
    BOOL bResult3= FALSE;
    BOOL bResult4= FALSE;
    BOOL bResult5= FALSE;
    
    ULONG_PTR sizemap= (size)/4096;
    ULONG_PTR sizemap2= (size)/4096;
    ULONG_PTR sizemap3= (size)/4096;
    ULONG_PTR sizemap4= (size)/4096;
    ULONG_PTR sizemap5= (size)/4096;
    
                if( ! LoggedSetLockPagesPrivilege( GetCurrentProcess(), TRUE ) )  
                /*. The SeLockMemoryPrivilege privilege must be enabled in the caller's token or 
                the function will fail with ERROR_PRIVILEGE_NOT_HELD*/
                  {
                    printf("\n No Previledge");
                    printf("\n %u", GetLastError() );
                      return;
                  }


    ULONG_PTR * aRAMPages = new ULONG_PTR[sizemap];
    ULONG_PTR * aRAMPages2 = new ULONG_PTR[sizemap2];
    ULONG_PTR * aRAMPages3 = new ULONG_PTR[sizemap3];
    ULONG_PTR * aRAMPages4 = new ULONG_PTR[sizemap4];
    ULONG_PTR * aRAMPages5 = new ULONG_PTR[sizemap5];


    ADD=VirtualAlloc(lpaddress,size,MEM_RESERVE | MEM_PHYSICAL,PAGE_READWRITE);

    
    if (ADD==0)

    {
    printf ("allocation failled");
    printf("\n %u", GetLastError() );
        return;
    }



    bResult=AllocateUserPhysicalPages(GetCurrentProcess(),&sizemap,aRAMPages);

            if( bResult != TRUE ) 
            {
            printf("\n %uError in AllocateUserPhysicalPages", GetLastError() );
            return;
            }

    bResult2=AllocateUserPhysicalPages(GetCurrentProcess(),&sizemap2,aRAMPages2);

            if( bResult != TRUE ) 
            {
            printf("\n %uError in AllocateUserPhysicalPages2", GetLastError() );
            return;
            }
    bResult3=AllocateUserPhysicalPages(GetCurrentProcess(),&sizemap3,aRAMPages3);

            if( bResult != TRUE ) 
            {
            printf("\n %uError in AllocateUserPhysicalPages2", GetLastError() );
            return;
            }

    bResult4=AllocateUserPhysicalPages(GetCurrentProcess(),&sizemap4,aRAMPages4);

            if( bResult != TRUE ) 
            {
            printf("\n %uError in AllocateUserPhysicalPages2", GetLastError() );
            return;
            }

    bResult5=AllocateUserPhysicalPages(GetCurrentProcess(),&sizemap5,aRAMPages5);

            if( bResult != TRUE ) 
            {
            printf("\n %uError in AllocateUserPhysicalPages2", GetLastError() );
            return;
            }


printf("\n We have allocated 5 different ranges of physical memory pages that could be used to map and unmapp within Address Windowing Extensions (AWE) region of a specified process");


printf("\n Mapping the first range and filling with : MAP1");

   bResult=MapUserPhysicalPages(ADD,sizemap,aRAMPages);

            if( bResult != TRUE ) 
            {
            printf("\n %uError in MapUserPhysicalPages", GetLastError() );
            return;
            }
            

        ADDw =(LPSTR) ADD;
        for(i=1;i<=(size-5);i=i+5)

        {
            ADDw[i] = 'M';
            ADDw[i+1] = 'A';
            ADDw[i+2] = 'P';
            ADDw[i+3] = '1';
            ADDw[i+4] = ':';

        }
 
    printf("\n Mapping the second range and filling with : MAP2");
    bResult2=MapUserPhysicalPages(ADD,sizemap2,aRAMPages2);
                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }

            for(i=1;i<=(size-5);i=i+5)
            {
            ADDw[i] = 'M';
            ADDw[i+1] = 'A';
            ADDw[i+2] = 'P';
            ADDw[i+3] = '2';
            ADDw[i+4] = ':';
            }
       
    printf("\n Mapping the third range and filling with : MAP3");            
       bResult3=MapUserPhysicalPages(ADD,sizemap3,aRAMPages3);

                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }

            for(i=1;i<=(size-5);i=i+5)
            {
            ADDw[i] = 'M';
            ADDw[i+1] = 'A';
            ADDw[i+2] = 'P';
            ADDw[i+3] = '3';
            ADDw[i+4] = ':';
            }
    
       printf("\n Mapped the fourth range and filling with : MAP4");    
            
        bResult4=MapUserPhysicalPages(ADD,sizemap4,aRAMPages4);
        
                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }

            for(i=1;i<=(size-5);i=i+5)
            {
            ADDw[i] = 'M';
            ADDw[i+1] = 'A';
            ADDw[i+2] = 'P';
            ADDw[i+3] = '4';
            ADDw[i+4] = ':';
            }
    
        printf("\n Mapped the fifth range and filled with : MAP5");

        bResult5=MapUserPhysicalPages(ADD,sizemap5,aRAMPages5);

                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }

            for(i=1;i<=(size-5);i=i+5)
            {
            ADDw[i] = 'M';
            ADDw[i+1] = 'A';
            ADDw[i+2] = 'P';
            ADDw[i+3] = '5';
            ADDw[i+4] = ':';
            }

         


    printf("\n Mapping the first range and printing First 128 charecters");

    bResult=MapUserPhysicalPages(ADD,sizemap,aRAMPages);
            
            if( bResult != TRUE ) 
            {
            printf("\n %uError in MapUserPhysicalPages", GetLastError() );
            return;
            }


            

                for(int i=1;i<=128;i++)
                {
                printf("%c",ADDw[i]);
                }

printf("\n Mapping the second range and printing First 128 charecters");            
     bResult2=MapUserPhysicalPages(ADD,sizemap2,aRAMPages2);

                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }
                for(int i=1;i<=128;i++)
                {
                printf("%c",ADDw[i]);
                }

printf("\n Mapping the third range and printing First 128 charecters");
bResult3=MapUserPhysicalPages(ADD,sizemap3,aRAMPages3);

                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }
                for(int i=1;i<=128;i++)
                {
                printf("%c",ADDw[i]);
                }

printf("\n Mapping the fourth range and printing First 128 charecters");
bResult4=MapUserPhysicalPages(ADD,sizemap4,aRAMPages4);

                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }
                for(int i=1;i<=128;i++)
                {
                printf("%c",ADDw[i]);
                }

printf("\n Mapping the five range and printing First 128 charecters");
bResult5=MapUserPhysicalPages(ADD,sizemap5,aRAMPages5);

                if( bResult != TRUE ) 
                {
                printf("\n %uError in MapUserPhysicalPages", GetLastError() );
                return;
                }
                for(int i=1;i<=128;i++)
                {
                printf("%c",ADDw[i]);
                }

system("pause");

}

 
 
 
Thanks
Karthick P.K

Posted in Programming | Tagged: , , , , , | 6 Comments »